Using Jakarta Security

We are working on a fresh, updated Jakarta EE Tutorial. This section hasn’t yet been updated.

This chapter describes the authentication and credential validation functionality provided by Jakarta Security. The API also defines a SecurityContext access point for programmatic security.

About Jakarta Security

Jakarta EE includes support for Jakarta Security, which defines portable, plug-in interfaces for authentication and identity stores, and a new injectable-type SecurityContext interface that provides an access point for programmatic security. You can use the built-in implementations of these APIs, or define custom implementations.

Jakarta Security contains the following packages:

Main Classes and Interfaces in jakarta.security.enterprise
Class or Interface Description

SecurityContext

Injectable-type interface that provides an access point for programmatic security intended to be used by application code to query and interact with the Jakarta Security.

CallerPrincipal

Principal type that can represent the identity of the application caller.

AuthenticationStatus

Enum used to indicate the return value from an authentication mechanism.

AuthenticationException

Indicates that a problem occurred during the authentication process.

Main Classes and Interfaces in jakarta.security.enterprise.authentication.mechanism.http
Class or Interface Description

HttpAuthenticationMechanism

Interface representing an HTTP authentication mechanism. Developers can provide their own implementation of this interface, or use one of several built-in HTTP authentication mechanisms.

HttpMessageContext

Interface representing the parameters passed to/from methods of an HttpAuthenticationMechanism at runtime.

AuthenticationParameters

Class that carries parameters passed to the SecurityContext.authenticate() method.

HttpMessageContextWrapper

Abstract class developers can extend to customize HttpMessageContext behavior.

The jakarta.security.enterprise.authentication.mechanism.http package also includes a number of annotation classes that are used to configure/enable the built-in authentication mechanisms or to modify the behavior of an authentication mechanism.
Main Classes and Interfaces in jakarta.security.enterprise.credential
Class or Interface Description

Credential

Interface that represents a generic credential and defines several methods to operate on credentials. All other classes in this package are implementations of the Credential interface.

AbstractClearableCredential

Abstract class implementing behavior common to Credentials that can be meaningfully cleared.

BasicAuthenticationCredential

Class that extends UsernamePasswordCredential to represent credentials used by HTTP Basic Authentication.

CallerOnlyCredential

Credential that contains a caller name only; can be used to assert an identity, but not to authenticate a user, due to the lack of any secret or other credential that can be validated.

Password

Class that represents a text-based password.

RememberMeCredential

Class that represents a credential presented as a token, for the explicit usage with the Jakarta Security remember me function.

UsernamePasswordCredential

Class that represents the credentials typically used by standard caller name/password authentication.

Main Classes and Interfaces in jakarta.security.enterprise.identitystore
Class or Interface Description

IdentityStore

Interface representing an Identity Store. Developers can provide their own implementation of this interface, or use one of the built-in Identity Stores.

IdentityStoreHandler

Interface that defines the method applications use to interact with Identity Stores. Applications can use the built-in IdentityStoreHandler, or supply their own implementation if custom behavior is desired.

PasswordHash

Interface defining methods for generating and validating password hashes, needed to securely validate passwords when using the built-in Database Identity Store. Developers can implement this interface to generate/validate password hashes using any desired algorithm.

Pbkdf2PasswordHash

Marker interface implemented by the built-in PBKDF2 PasswordHash implementation. Developers can use this interface to select the built-in PBKDF2 algorithm when configuring the Database Identity Store.

RememberMeIdentityStore

Interface defining a special type of Identity Store, used in conjunction with the RememberMe annotation to provide RememberMe behavior for an application.

CredentialValidationResult

Class that represents the result from an attempt to validate a Credential.

IdentityStorePermission

Permission required to invoke the getGroups method of an IdentityStore, when a SecurityManager is configured.

Overview of the HTTP Authentication Mechanism Interface

The HttpAuthenticationMechanism interface defines an SPI for writing authentication mechanisms that can be provided with an application and deployed using CDI. Developers can write their own implementations of HttpAuthenticationMechanism to support specific authentication token types or protocols. There are also several built-in authentication mechanisms that perform BASIC, FORM, and Custom FORM authentication.

The built-in authentication mechanisms are enabled and configured through the use of one of the following annotations:

  • BasicAuthenticationMechanismDefinition — implements BASIC authentication that conforms to the behavior of the servlet container when BASIC <auth-method> is declared in web.xml.

  • FormAuthenticationMechanismDefinition — implements FORM authentication that conforms to the behavior of the servlet container when the FORM <auth-method> is declared in web.xml.

  • CustomFormAuthenticationMechanismDefinition — implements a modified version of FORM authentication in which custom handling replaces the POST to j_security_check.

An implementation of HttpAuthenticationMechanism must be a CDI bean to be recognized and deployed at runtime, and is assumed to be normal scoped. During bean discovery, the servlet container looks for a bean that implements HttpAuthenticationMechanism — there should be only one per application — and, if found, arranges for it to be deployed to authenticate the application’s callers.

The servlet container leverages Jakarta Authentication to deploy authentication mechanisms. The container provides a Jakarta Authentication Server Auth Module (SAM) that can delegate to an HttpAuthenticationMechanism, and arranges for that "bridge" SAM to be registered with the Jakarta Authentication AuthConfigFactory. At runtime, normal Jakarta Authentication processing invokes the bridge SAM, which then delegates to the HttpAuthenticationMechanism to perform the authentication and drive any necessary dialog with the caller, or with third parties involved in the authentication protocol flow.

The HttpAuthenticationMechanism interface defines the following three methods, which correspond to the three methods defined by the Jakarta Authentication ServerAuth interface. When one of the Jakarta Authentication methods is invoked on the bridge SAM, it delegates to the corresponding method of the HttpAuthenticationMechanism. Although the method names are identical, the method signatures are not; the bridge SAM maps back and forth between the parameters passed to it by the Jakarta Authentication framework, and the parameters expected by an HttpAuthenticationMechanism.

  • validateRequest() — validate an incoming request and authenticates the caller.

  • secureResponse() — (optional if default is sufficient) secure a response message.

  • cleanSubject() — (optional if default is sufficient) clear the provided Subject of principals and credentials.

Only the validateRequest() method must be implemented by an HttpAuthenticationMechanism; the interface includes default implementations for secureResponse() and cleanSubject() that will often be sufficient.

The following annotations can be used to add additional behaviors to an HttpAuthenticationMechanism:

  • AutoApplySession — indicates that the Jakarta Authentication registerSession functionality should be enabled such that the the caller’s authenticated identity is persisted in the caller’s servlet session.

  • LoginToContinue — mechanism to specify properties for FORM login — login page, error page, etc. The built-in FORM authentication mechanisms use LoginToContinue to configure the necessary parameters.

  • RememberMe — specifies that a RememberMe identity store should be used to enable RememberMe functionality for the authentication mechanism.

Overview of the Identity Store Interfaces

The Identity Store Interfaces are described in the following sections:

The IdentityStore Interface

The IdentityStore interface defines an SPI for interacting with identity stores, which are directories or databases containing user account information. An implementation of the IdentityStore interface can validate users' credentials, provide information about the groups they belong to, or both. Most often, an IdentityStore implementation will interact with an external identity store — an LDAP server, for example — to perform the actual credential validation and group lookups, but an IdentityStore may also manage user account data itself.

There are two built-in implementations of IdentityStore: an LDAP identity store, and a Database identity store. These identity stores delegate to external stores that must already exist; the IdentityStore implementations do not provide or manage the external store. They are configured with the parameters necessary to communicate with an external store using the following annotations:

  • LdapIdentityStoreDefinition — configures an identity store with the parameters necessary to communicate with an external LDAP server, validate user credentials, and/or lookup user groups.

  • DatabaseIdentityStoreDefinition — configures an identity store with the parameters necessary to connect to an external database, validate user credentials, and/or lookup user groups. You must supply a PasswordHash implementation when configuring a Database Identity Store. See The PasswordHash Interface.

An application can provide its own custom identity store, or use the built-in LDAP or database identity stores. For examples of both types, see:

An implementation of IdentityStore must be a CDI bean to be recognized and deployed at runtime, and is assumed to be normal scoped. IdentityStores are primarily intended for use by implementations of HttpAuthenticationMechanisms, but this is not a requirement. They can be used by other types of authentication mechanisms as well, or by containers.

Multiple implementations of IdentityStore may be present. If so, they are invoked under the control of an IdentityStoreHandler.

IdentityStoreHandler

Authentication mechanisms do not interact with IdentityStore directly; instead, they call an IdentityStoreHandler. An implementation of the IdentityStoreHandler interface provides a single method, validate(Credential), which, when invoked, iterates over the available IdentityStores and returns an aggregated result. An IdentityStoreHandler must also be a CDI bean, and is assumed to be normal scoped. At runtime, an authentication mechanism injects the IdentityStoreHandler and invokes on it. The IdentityStoreHandler, in turn, looks up the available IdentityStores and invokes on them to determine the aggregate result.

There is a built-in IdentityStoreHandler that implements a standard algorithm defined by Jakarta Security. The Jakarta Security specification provides a full description of the algorithm, but it can be roughly summarized as follows:

  • Iterate over the available validating IdentityStores, in priority order, until the provided Credential is validated or there are no more IdentityStores.

  • If the Credential was validated, iterate over the available group-providing IdentityStores, in priority order, aggregating the groups returned by each store.

  • Return the validated caller and group information.

An application may also supply its own IdentityStoreHandler, which can use any desired algorithm to select and invoke on IdentityStores, and return an aggregated (or non-aggregated) result.

IdentityStore Interface Methods

The IdentityStore interface itself has four methods:

  • validate(Credential) — validate a Credential, and return the result of that validation.

  • getCallerGroups(CredentialValidationResult) — return the groups associated with the caller indicated by the supplied CredentialValidationResult, which represents the result of a previous, successful validation.

  • validationTypes() — returns a Set of validation types (one or more of VALIDATE, PROVIDE_GROUPS) that indicate the operations supported by this instance of the IdentityStore.

  • priority() — returns a positive integer representing the self-declared priority of this IdentityStore. Lower values represent higher priority.

Because getCallerGroups() is a sensitive operation — it can return information about arbitrary users, and does not require that the caller provide the user’s credential or proof of identity — the caller should have the IdentityStorePermission("getGroups") permission. Enforcement of this check is incumbent on the implementation of the getCallerGroups() method; the built-in IdentityStores do check for this permission, if a SecurityManager is configured, and the built-in IdentityStoreHandler invokes the getCallerGroups() method in the context of a PrivilegedAction block.

The PasswordHash Interface

Unlike some types of identity stores, for example LDAP directories, databases can store and retrieve user passwords, but can’t verify them natively. Therefore, the built-in Database identity store must verify user passwords itself. Most often, this involves generating a hash of the user’s password for comparison with a hash value stored in the database.

In order to provide maximum flexibility and interoperability, the Database identity store does not implement any specific password hashing algorithms. Instead, it defines the PasswordHash interface, and expects the application to provide an implementation of PasswordHash that can verify passwords from the specific store the application will use. The PasswordHash implementation must be made available as a dependent-scoped bean, and is configured by providing the fully-qualified name of the desired type as the hashAlgorithm value on a DatabaseIdentityStoreDefinition.

The PasswordHash algorithm defines three methods:

  • initialize(Map<String,String> parameters) — initialize the PasswordHash with the supplied Map of parameters. The Database identity store calls this method when initializing, passing the hashAlgorithmParameters value of the DatabaseIdentityStoreDefinition annotation (after conversion to a Map).

  • verify(char[] password, String hashedPassword) — verify a caller-supplied password against the caller’s stored password hash as retrieved from the database. The hashedPassword value should be provided exactly as it was returned from the database.

  • generate(char[] password) — generate a password hash from the supplied password. The value returned should be formatted and encoded exactly it would be stored in the database. While it is useful to generate the hash of a caller-supplied password during verify(), this method is intended primarily for use by applications or IdentityStore implementations that want to support password management/reset capability without having to duplicate the code used to verify passwords.

Note that, while the interface is oriented toward hashing passwords, it can also support alternative approaches, such as two-way encryption of stored passwords.

There is a built-in Pbkdf2PasswordHash implementation that supports, as it’s name suggests, PBKDF2 password hashing. It supports several parameters that control the generation of hash values (key size, iterations, and so on — see the Javadoc), and those parameters are encoded into the resulting hash value, so that hashes can be verified even if the currently configured parameters are different from the parameters in effect when a stored hash was generated.

While it is necessary to write a custom PasswordHash to enable interoperability with a legacy identity store that stores password hashes in a format other than the Pbkdf2PasswordHash format, developers should consider carefully whether Pbkdf2PasswordHash is sufficient for new identity stores, and avoid writing a new PasswordHash implementation without a solid understanding of the cryptographic and other security considerations involved. Some of the considerations specific to password hashing are:

  • The requirements for hashing passwords differ considerably from the requirements for hashing in other contexts. In particular, speed is normally a virtue when generating hashes, but when generating password hashes, slower is better — to slow down brute force attacks against hashed values.

  • The comparison of a generated hash with a stored hash should take constant time, whether it succeeds or fails, in order to avoid giving an attacker clues about the password value based on the timing of failed attempts.

  • A new random salt should be used each time a new password hash value is generated.

The RememberMeIdentityStore Interface

The RememberMeIdentityStore interface represents a special type of identity store. It is not directly related to the IdentityStore interface; that is, it does not implement or extend it. It does, however, perform a similar, albeit specialized, function.

In some cases, an application wants to "remember" a user’s authenticated session for an extended period. For example, a web site may remember you when you visit, and prompt for your password only periodically, perhaps once every two weeks, as long as you don’t explicitly log out.

RememberMe works as follows:

  • When a request from an unauthenicated user is received, the user is authenticated using an HttpAuthenticationMechanism that is provided by the application (this is required — RememberMeIdentityStore can only be used in conjunction with an application-supplied HttpAuthenticationMechanism).

  • After authentication, the configured RememberMeIdentityStore saves information about the user’s authenticated identity, so that it be restored later, and generates a long-lived "remember me" login token that is sent back to the client, perhaps as a cookie.

  • On a subsequent visit to the application, the client presents the login token. The RememberMeIdentityStore then validates the token and returns the stored user identity, which is then established as the user’s authenticated identity. If the token is invalid or expired, it is discarded, the user is authenticated normally again, and a new login token is generated.

The RememberMeIdentityStore interface defines the following methods:

  • generateLoginToken(CallerPrincipal caller, Set<String> groups) — generate a login token for a newly authenticated user, and associate it with the provided caller/group information.

  • removeLoginToken(String token) — remove the (presumably expired or invalid) login token and any associated caller/group information.

  • validate(RememberMeCredential credential) — validate the supplied credential, and, if valid, return the associated caller/group information. (RememberMeCredential is essentially just a holder for a login token).

An implementation of RememberMeIdentityStore must be a CDI bean, and is assumed to be normal scoped. It is configured by adding a RememberMe annotation to an application’s HttpAuthenticationMechanism, which indicates that a RememberMeIdentityStore is in use, and provides related configuration parameters. A container-supplied interceptor then intercepts calls to the HttpAuthenticationMechanism, invokes the RememberMeIdentityStore as necessary before and after calls to the authentication mechanism, and ensures that the user’s identity is correctly set for the session. The Jakarta Security specification provides a detailed description of the required interceptor behavior.

Implementations of RememberMeIdentityStore should take care to manage tokens and user identity information securely. For example, login tokens should not contain sensitive user information, like credentials or sensitive attributes, to avoid exposing that information if an attacker were able to gain access to the token — even an encrypted token is potentially vulnerable to an attacker with sufficient time/resources. Similarly, tokens should be encrypted/signed wherever possible, and sent only over secure channels (HTTPS). User identity information managed by a RememberMeIdentityStore should be stored as securely as possible (but does not necessarily need to be reliably persisted — the only impact of a "forgotten" session is that the user will be prompted to log in again).

Running the Built-In Database Identity Store Example

The example described in this section demonstrates how to use the built-in database identity store for credential validation.

Topics include:

Overview of the Built-In Database Identity Store Example

Jakarta Security mandates that a Jakarta EE container MUST support a built-in IdentityStore backed by a database. To support this mandatory requirement, DatabaseIdentityStore is bundled with GlassFish.

This example demonstrates how you can configure a DatabaseIdentityStore to point to a backend database and then use it as an IdentityStore. The authentication mechanism used is BasicAuthenticationMechanism.

The source code for this example is in the jakartaee-examples/tutorial/security/security-api/built-in-db-identity-store directory.

The following sections describe the high-level process for configuring the DatabaseIdentityStore. Note that the configuration described in these sections has already been completed in the application files, but is provided here to illustrate what you need to do to use the built-in database identity store.

When a request that includes credentials is sent to the application, it triggers the configured authentication mechanism and authentication is performed against the DatabaseIdentityStore as defined in the application.

Post authentication, the application also verifies the roles the caller is in and sends the details as part of the response.

Note that in GlassFish, if the user provides the wrong credentials when using BasicAuthenticationMechanism, then the realmName is presented to user, as a hint.

curl -I -u Joe http://localhost:8080/built-in-db-identity-store/servlet
Enter host password for user 'Joe':
HTTP/1.1 401 Unauthorized
Server: Eclipse GlassFish  6.0.0
X-Powered-By: Servlet/5.0 JSP/3.0(Eclipse GlassFish  6.0.0  Java/AdoptOpenJDK/1.8)
WWW-Authenticate: Basic realm="file"
Content-Length: 1056
Content-Language:
Content-Type: text/html

Define the Users and Groups in the Identity Store

The following table shows the users, passwords, and groups used in this example.

User Password Group

Joe

secret1

foo, bar

Sam

secret2

foo, bar

Tom

secret2

foo

Sue

secret2

foo

The following code shows how to define credentials and the roles assigned to users in the DatabaseSetup.java file.

With @Startup annotation, this singleton enterprise bean is initialized during application startup and the credentials are set in the underlying database.

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.annotation.Resource;
import jakarta.annotation.sql.DataSourceDefinition;
import jakarta.ejb.Singleton;
import jakarta.ejb.Startup;
import jakarta.sql.DataSource;

@Singleton
@Startup
public class DatabaseSetup {

    // The default datasource that is bundled with GlassFish is used to store // credentials.
    @Resource(lookup="java:comp/DefaultDataSource")
    private DataSource dataSource;

    @PostConstruct
    public void init() {

        // ...
        executeUpdate(dataSource, "INSERT INTO caller VALUES('Joe', '" + passwordHash.generate("secret1".toCharArray()) + "')");
        // ...
        executeUpdate(dataSource, "INSERT INTO caller_groups VALUES('Joe', 'foo')");
        executeUpdate(dataSource, "INSERT INTO caller_groups VALUES('Joe', 'bar')");
        // ...
    }

    @PreDestroy
    public void destroy() {
    	// ...
    }

    private void executeUpdate(DataSource dataSource, String query) {
        // ...
    }
}

Map the DatabaseIdentityStore to the Default Data source

Use the @DatabaseIdentityStoreDefinition annotation to map the built-in DatabaseIdentityStore to the DefaultDataSource in the ApplicationConfig.java file. This example also demonstrates the use of the Pbkdf2PasswordHash interface.

// Database Definition for built-in DatabaseIdentityStore
@DatabaseIdentityStoreDefinition(
    callerQuery = "#{'select password from caller where name = ?'}",
    groupsQuery = "select group_name from caller_groups where caller_name = ?",
    hashAlgorithm = Pbkdf2PasswordHash.class,
    priorityExpression = "#{100}",
    hashAlgorithmParameters = {
        "Pbkdf2PasswordHash.Iterations=3072",
        "${applicationConfig.dyna}"
    }
)

@ApplicationScoped
@Named
public class ApplicationConfig {

  public String[] getDyna() {
       return new String[]{"Pbkdf2PasswordHash.Algorithm=PBKDF2WithHmacSHA512", "Pbkdf2PasswordHash.SaltSizeBytes=64"};
   }

}

Specify the Authentication Mechanism

In this application, credentials are validated using the BASIC authentication mechanism. Specify the @BasicAuthenticationMechanismDefinition annotation in the ApplicationConfig.java to ensure that the BasicAuthenticationMechanism is used to perform credential validation.

When a request is made to the servlet in question, the container delegates the request to org.glassfish.soteria.mechanisms.jaspic.HttpBridgeServerAuthModule, which then invokes the BasicAuthenticationMechanism#validateRequest method, and gets the credential from the request.

@BasicAuthenticationMechanismDefinition(
        realmName = "file"
)

Declare Roles in the Servlet Container

When a request is made to the application, the roles the user is in are returned as part of the response. Note that the container needs to be made aware of the supported roles, which are defined using the @DeclareRoles({ "foo", "bar", "kaz" }) annotation as shown below.

@WebServlet("/servlet")
@DeclareRoles({ "foo", "bar", "kaz" })
@ServletSecurity(@HttpConstraint(rolesAllowed = "foo"))
public class Servlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        String webName = null;
        if (request.getUserPrincipal() != null) {
            webName = request.getUserPrincipal().getName();
        }

        response.getWriter().write("web username: " + webName + "\n");

        response.getWriter().write("web user has role \"foo\": " + request.isUserInRole("foo") + "\n");
        response.getWriter().write("web user has role \"bar\": " + request.isUserInRole("bar") + "\n");
        response.getWriter().write("web user has role \"kaz\": " + request.isUserInRole("kaz") + "\n");
    }

}

In GlassFish 6.0, group to role mapping is enabled by default. Therefore, you do not need to bundle web.xml with the application to provide mapping between roles and groups.

Running the built-in-db-identity-store Example

You can use either NetBeans IDE or Maven to build, package, deploy, and run the built-in-db-identity-store application as described in the following topics:

To Build, Package, and Deploy the built-in-db-identity-store Example Using NetBeans IDE

  1. If you have not already done so, start the default database. This is necessary because we are using the DefaultDataSource bundled with GlassFish for DatabaseIdentityStore. See Starting and Stopping Apache Derby.

  2. If you have not already done so, start the GlassFish server. See Starting and Stopping GlassFish Server.

  3. From the File menu, choose Open Project.

  4. In the Open Project dialog box, navigate to:

    jakartaee-examples/tutorial/security/security-api
  5. Select the built-in-db-identity-store folder.

  6. Click Open Project.

  7. In the Projects tab, right-click the built-in-db-identity-store project and select Build.

    This command builds and deploys the example application to your GlassFish Server instance.

To Build, Package, and Deploy the built-in-db-identity-store Example Using Maven

  1. If you have not already done so, start the default database. This is necessary because we are using the DefaultDataSource bundled with GlassFish for DatabaseIdentityStore. See Starting and Stopping Apache Derby.

  2. If you have not already done so, start the GlassFish server. See Starting and Stopping GlassFish Server.

  3. In a terminal window, go to:

    jakartaee-examples/tutorial/security/security-api/built-in-db-identity-store
  4. Enter the following command:

    mvn install

    This command builds and packages the application into a WAR file, built-in-db-identity-store.war, that is located in the target directory, then deploys the WAR file.

To Run the built-in-db-identity-store Example

In this example, use the credentials of user Joe to make a request and to validate the response according to the credentials/roles defined in DatabaseSetup.java.

  1. Make a request to the deployed application by entering the following request URL in your web browser:

    Request URL:

    http://localhost:8080/built-in-db-identity-store/servlet

    Because BASIC authentication is being used here, the container responds back prompting for username and password.

  2. Enter the username Joe, and the password secret1 at the prompt.

    Once you provide the credentials, the following process occurs:

    • The client presents the request to the container with base64 encoded string and with the Authorization header using the value in the format expected for basic authentication.

    • With the username and password available to the container, validation is performed against DatabaseIdentityStore.

    • The corresponding UsernamePasswordCredential object is passed as a parameter to the DatabaseIdentityStore#validate() method.

    • The password is fetched from the database for user Joe.

    • The password stored in the database is hashed using the PBKDF2 algorithm and verified by the built-in Pbkdf2PasswordHash implementation.

    • On successful verification, the request gets delegated to the servlet in question and the following response is returned to the end user.

      Response:

      web username: Joe
      web user has role "foo": true
      web user has role "bar": true
      web user has role "kaz": false
  3. Test the authentication using invalid credentials. Make a request to the deployed application by entering the following request URL in your web browser:

    Request URL:

    http://localhost:8080/built-in-db-identity-store/servlet

    Again, because BASIC authentication is being used here, the container responds back prompting for username and password.

  4. Enter an invalid username and password. You are promted to enter the credentials again, but you are not authenticated.

    When you click Cancel in the Authentication required window, the following response is returned:

    HTTP Status 401 - Unauthorized
    
    type Status report
    
    message Unauthorized
    
    description This request requires HTTP authentication.
    
    Eclipse GlassFish 6.0.0

Running the Custom Identity Store Example

The example described in this section demonstrates how to bundle and use a custom identity store in your application for credential validation.

Topics include:

Overview of the Custom Identity Store Example

As an alternative to using a built-in identity store, an application can provide its own IdentityStore. When bundled with the application, this custom identity store can then be used for authentication and authorization.

This example demonstrates how to define a custom identity store, TestIdentityStore, and provide it as part of the application being deployed. The authentication mechanism used is BasicAuthenticationMechanism.

The source code for this example is in the jakartaee-examples/tutorial/security/security-api/custom-identity-store directory.

The following sections describe the high-level process for defining the TestIdentityStore. Note that the configuration described in these sections has already been completed in the application files, but is provided here to illustrate what you need to do to use a custom identity store.

When a request that includes credentials is sent to the application, the configured authentication mechanism comes into effect and authentication is performed against the TestIdentityStore as defined in the application.

Post authentication, the application also verifies the roles the caller is in and sends the details as part of the response.

Note that in GlassFish, if the user provides the wrong credentials when using BasicAuthenticationMechanism, then the realmName is presented to user, as a hint.

curl -I -u Joe http://localhost:8080/custom-identity-store/servlet
Enter host password for user 'Joe':
HTTP/1.1 401 Unauthorized
Server: Eclipse GlassFish  6.0.0
X-Powered-By: Servlet/5.0 JSP/3.0(Eclipse GlassFish  6.0.0  Java/AdoptOpenJDK/1.8)
WWW-Authenticate: Basic realm="file"
Content-Length: 1056
Content-Language:
Content-Type: text/html

Define the Users and Groups in the Identity Store

The following table shows the user, password, and group used in this example.

User Password Group

Joe

secret1

foo, bar

The following code snippet shows how you define the credentials and the roles assigned to users in the TestIdentityStore.java file.

if (usernamePasswordCredential.compareTo("Joe", "secret1")) {
    return new CredentialValidationResult("Joe", new HashSet<>(asList("foo", "bar")));
}

Specify the Authentication Mechanism

In this application, credentials are validated using the BASIC authentication mechanism. Specify the @BasicAuthenticationMechanismDefinition annotation in the ApplicationConfig.java to ensure that the BasicAuthenticationMechanism is used to perform credential validation.

@BasicAuthenticationMechanismDefinition(
        realmName = "file"
)

@ApplicationScoped
@Named
public class ApplicationConfig {

}

Declare Roles in the Servlet Container

When a request is made to the application, the roles the user is in are returned as part of the response. Note that the container needs to be made aware of the supported roles, which are defined using the @Declareroles({ "foo", "bar", "kaz" }) annotation as shown below.

@DeclareRoles({ "foo", "bar", "kaz" })
@WebServlet("/servlet")
public class Servlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        String webName = null;
        if (request.getUserPrincipal() != null) {
            webName = request.getUserPrincipal().getName();
        }

        response.getWriter().write("web username: " + webName + "\n");

        response.getWriter().write("web user has role \"foo\": " + request.isUserInRole("foo") + "\n");
        response.getWriter().write("web user has role \"bar\": " + request.isUserInRole("bar") + "\n");
        response.getWriter().write("web user has role \"kaz\": " + request.isUserInRole("kaz") + "\n");
    }

}

In GlassFish 6.0, group to role mapping is enabled by default. Therefore, you do not need to bundle web.xml with the application to provide mapping between roles and groups.

Running the custom-identity-store Example

You can use either NetBeans IDE or Maven to build, package, deploy, and run the custom-identity-store application as described in the following topics:

To Build, Package, and Deploy the custom-identity-store Example Using NetBeans IDE

  1. If you have not already done so, start the GlassFish server. See Starting and Stopping GlassFish Server.

  2. From the File menu, choose Open Project.

  3. In the Open Project dialog box, navigate to:

    jakartaee-examples/tutorial/security/security-api
  4. Select the custom-identity-store folder.

  5. Click Open Project.

  6. In the Projects tab, right-click the custom-identity-store project and select Build.

    This command builds and deploys the example application to your GlassFish Server instance.

To Build, Package, and Deploy the custom-identity-store Example Using Maven

  1. If you have not already done so, start the GlassFish server. See Starting and Stopping GlassFish Server.

  2. In a terminal window, go to:

    jakartaee-examples/tutorial/security/security-api/custom-identity-store
  3. Enter the following command:

    mvn install

    This command builds and packages the application into a WAR file, custom-identity-store.war, that is located in the target directory, then deploys the WAR file.

To Run the custom-identity-store Example

In this example, use the credentials of user Joe to make a request and to validate the response according to the credentials defined in TestIdentityStore.

  1. Make a request to the deployed application using valid credentials by entering the following request URL in your web browser:

    Request URL:

    http://localhost:8080/custom-identity-store/servlet?name=Joe&password=secret1

    Response:

    web username: Joe
    web user has role "foo": true
    web user has role "bar": true
    web user has role "kaz": false
  2. Test the authentication using invalid credentials. Make a request to the deployed application by entering the following request URL in your web browser:

    Request URL:

    http://localhost:8080/custom-identity-store/servlet?name=Joe&password=secret3

    Response:

    HTTP Status 401 - Unauthorized
    
    type Status report
    
    message Unauthorized
    
    description This request requires HTTP authentication.
    
    Eclipse GlassFish 6.0.0