Running the Built-In Database Identity Store Example (original) (raw)
The example described in this section demonstrates how to use the built-in database identity store for credential validation.
Overview of the Built-In Database Identity Store Example
JSR 375 mandates that a Java EE container MUST support a built-in IdentityStore
backed by a database. To support this mandatory requirement, DatabaseIdentityStore
is bundled with the GlassFish-RI.
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 isBasicAuthenticationMechanism
.
The source code for this example is in the_tut-install_/examples/security/security-api/built-in-db-identity-store
directory.
The following sections describe the high-level process for configuring theDatabaseIdentityStore
. 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.
- Define the Users and Groups in the Identity Store
- Map the DatabaseIdentityStore to the Default Data source
- Specify the Authentication Mechanism
- Declare Roles in the Servlet Container
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 usingBasicAuthenticationMechanism
, 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: GlassFish Server Open Source Edition 5.0
X-Powered-By: Servlet/3.1 JSP/2.3 (GlassFish Server Open Source Edition 5.0 Java/Oracle Corporation/1.8)
WWW-Authenticate: Basic realm="file"
Content-Length: 1090
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 javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.annotation.sql.DataSourceDefinition;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.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 thePbkdf2PasswordHash
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 5.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
- To Build, Package, and Deploy the built-in-db-identity-store Example Using Maven
- To Run the built-in-db-identity-store Example
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. SeeStarting and Stopping GlassFish Server.
- From the File menu, choose Open Project.
- In the Open Project dialog box, navigate to:
tut-install/examples/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. SeeStarting and Stopping GlassFish Server.
- In a terminal window, go to:
tut-install/examples/security/security-api/built-in-db-identity-store
- Enter the following command:
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 inDatabaseSetup.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.
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 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.
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.
GlassFish Server Open Source Edition 5