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:
-
The
jakarta.security.enterprise
package is the main Jakarta Security package and contains classes and interfaces that span authentication, authorization, and identity concerns. Main Classes and Interfaces injakarta.security.enterprise
lists the main class and interfaces in this package. -
The
jakarta.security.enterprise.authentication.mechanism.http
package contains classes and interfaces associated with HTTP-based authentication mechanisms that can interact with a caller or third-parties as part of an authentication protocol. Main Classes and Interfaces injakarta.security.enterprise.authentication.mechanism.http
lists the main classes and interfaces in this package. -
The
jakarta.security.enterprise.credential
package contains classes and interfaces for representing user credentials. Main Classes and Interfaces injakarta.security.enterprise.credential
lists the main classes and interfaces in this package. -
The
jakarta.security.enterprise.identitystore
package contains classes and interfaces associated with identity stores that validate a caller’s credentials and lookup caller groups. Main Classes and Interfaces injakarta.security.enterprise.identitystore
lists the main classes and interfaces in this package.
Class or Interface | Description |
---|---|
|
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. |
|
Principal type that can represent the identity of the application caller. |
|
Enum used to indicate the return value from an authentication mechanism. |
|
Indicates that a problem occurred during the authentication process. |
Class or Interface | Description |
---|---|
|
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. |
|
Interface representing the parameters passed to/from methods of an |
|
Class that carries parameters passed to the |
|
Abstract class developers can extend to customize |
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.
|
Class or Interface | Description |
---|---|
|
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. |
|
Abstract class implementing behavior common to Credentials that can be meaningfully cleared. |
|
Class that extends |
|
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. |
|
Class that represents a text-based password. |
|
Class that represents a credential presented as a token, for the explicit usage with the Jakarta Security remember me function. |
|
Class that represents the credentials typically used by standard caller name/password authentication. |
Class or Interface | Description |
---|---|
|
Interface representing an Identity Store. Developers can provide their own implementation of this interface, or use one of the built-in Identity Stores. |
|
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. |
|
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. |
|
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. |
|
Interface defining a special type of Identity Store, used in conjunction with the RememberMe annotation to provide RememberMe behavior for an application. |
|
Class that represents the result from an attempt to validate a Credential. |
|
Permission required to invoke the |
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 AuthenticationregisterSession
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 aRememberMe
identity store should be used to enableRememberMe
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 suppliedCredentialValidationResult
, which represents the result of a previous, successful validation. -
validationTypes()
— returns a Set of validation types (one or more ofVALIDATE
,PROVIDE_GROUPS
) that indicate the operations supported by this instance of theIdentityStore
. -
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 thehashAlgorithmParameters
value of theDatabaseIdentityStoreDefinition
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. ThehashedPassword
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 duringverify()
, this method is intended primarily for use by applications orIdentityStore
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-suppliedHttpAuthenticationMechanism
). -
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
-
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. -
If you have not already done so, start the GlassFish server. See Starting and Stopping GlassFish Server.
-
From the File menu, choose Open Project.
-
In the Open Project dialog box, navigate to:
jakartaee-examples/tutorial/security/security-api
-
Select the
built-in-db-identity-store
folder. -
Click Open Project.
-
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
-
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. -
If you have not already done so, start the GlassFish server. See Starting and Stopping GlassFish Server.
-
In a terminal window, go to:
jakartaee-examples/tutorial/security/security-api/built-in-db-identity-store
-
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 thetarget
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
.
-
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.
-
Enter the username
Joe
, and the passwordsecret1
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 theDatabaseIdentityStore#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-inPbkdf2PasswordHash
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
-
-
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.
-
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
-
If you have not already done so, start the GlassFish server. See Starting and Stopping GlassFish Server.
-
From the File menu, choose Open Project.
-
In the Open Project dialog box, navigate to:
jakartaee-examples/tutorial/security/security-api
-
Select the
custom-identity-store
folder. -
Click Open Project.
-
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
-
If you have not already done so, start the GlassFish server. See Starting and Stopping GlassFish Server.
-
In a terminal window, go to:
jakartaee-examples/tutorial/security/security-api/custom-identity-store
-
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 thetarget
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
.
-
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
-
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