Skip navigation

Elytron currently supports aggregate security realms which are an aggregate of an authentication-realm and one or more authorization-realms. The authentication-realm is used to load the credentials of an identity or perform evidence verification and the authorization-realms are used to load the identity’s attributes used for authorization. The principal undergoes a series of transformations before being used to load the authentication identity from the aggregate-realm.

 

The aggregate-realm now supports the attribute principal-transformer. This attribute is optional and allows the user to configure an additional transformation for the principal to undergo after the authentication identity is loaded but before the authorization identity is loaded.

 

The principal-transformer attribute references a transformer defined in the mappers configuration. This transformer can then be used when configuring an aggregate realm.

 

This blog post will be making use of:

 

elytron-examples/aggregate-realm-principal-transformer at master · wildfly-security-incubator/elytron-examples · GitHub

 

This example shows how to configure an aggregate-realm with a principal-transformer and how to secure a webapp using the aggregated realm for authentication and authorization.

 

Configuring the server

 

A WildFly CLI script that contains all of the commands that are used in this example can be found in the aggregate-realm-principal-transformer project in the elytron-examples repository.

 

Our first step will be to create our authentication and authorization realms, both of which will be filesystem realms.

 

The following command will create the authentication realm:

 

/subsystem=elytron/filesystem-realm=authenticationRealm:add(path=authenticationFS,relative-to=jboss.server.config.dir,encoded=false)

 

We will then add the identity with the name “guest” and password “guestPwd1!” to the authentication realm:

 

/subsystem=elytron/filesystem-realm=authenticationRealm:add-identity(identity="guest")

 

/subsystem=elytron/filesystem-realm=authenticationRealm:set-password(identity="guest", clear={password="guestPwd1!"})

 

Next, we create our authorization realm and add an identity with the name “guest-attributes” and the attribute ‘Roles=Users’:

 

/subsystem=elytron/filesystem-realm=authorizationRealm:add(path=authorizationFS,relative-to=jboss.server.config.dir,encoded=false)

 

/subsystem=elytron/filesystem-realm=authorizationRealm:add-identity(identity="guest-attributes")

 

/subsystem=elytron/filesystem-realm=authorizationRealm:add-identity-attribute(identity="guest-attributes",name=Roles,value=[Users])

 

We now have our two realms to be aggregated: authenticationRealm to load the credentials for the identity and authorizationRealm to load the attributes used for authorization. However, the principal for the identities is different for the two realms! To remedy this, we can add a constant principal transformer which will always return the principal “guest-attributes”:

 

/subsystem=elytron/constant-principal-transformer=constantTransformer:add(constant="guest-attributes")

 

We can then configure our aggregate realm using our two new filesystem realms and principal transformer:

 

/subsystem=elytron/aggregate-realm=aggregateRealm:add(authentication-realm=authenticationRealm,authorization-realm=authorizationRealm,principal-transformer=constantTransformer)

 

Now our aggregate realm will use  the principal “guest” to load the credentials from the authentication realm and then transform the principal to “guest-attributes” to load the attributes from the authorization realm.

 

We then add a security domain which makes use of our aggregate realm:

 

/subsystem=elytron/security-domain=simpleDomain:add(realms=[{realm=aggregateRealm}],default-realm=aggregateRealm,permission-mapper=default-permission-mapper)

 

We also configure a http-authentication-factory to secure our web application with our new domain:

 

/subsystem=elytron/http-authentication-factory=http-auth:add(http-server-mechanism-factory=global,security-domain=simpleDomain,mechanism-configurations=[{mechanism-name=BASIC,mechanism-realm-configurations=[{realm-name=other}]}])

 

/subsystem=undertow/application-security-domain=other:add(http-authentication-factory=http-auth)

 

Finally, we reload the server:

 

reload

 

Note: There is now the option to aggregate the attributes of an identity from multiple authorization realms. The principal used for all the authorization realms will be the same one (i.e. the transformed principal if a principal transformer is defined).

 

Deploying and accessing the application

 

We’re going to make use of the simple-webapp project. This application allows you to access a secured servlet if your identity has the attribute Roles=Users.  It can be deployed using the following commands:

 

cd $PATH_TO_ELYTRON_EXAMPLES/simple-webapp

mvn clean install wildfly:deploy

 

You can access the application at http://localhost:8080/simple-webapp/.

Try logging in with the username: “guest” and password: “guestPwd1!”

You are successfully authorized to access the secured application, which means our transformer worked and the attribute Roles=Users was successfully loaded from the authorization-realm using the principal that was transformed from ‘guest’ to ‘guest-attributes’.

 

Summary

This blog post has shown how to use the new optional attribute principal-transformer in an aggregate-realm to transform the principal between the authentication-realm and authorization-realm.

 

For more information on aggregate realms checkout:

 

wildfly/Aggregate_Security_Realm.adoc at master · wildfly/wildfly · GitHub

The Elytron Client allows remote clients to authenticate using Elytron. The configurations for these clients can be specified in two ways: using an XML configuration file or using a programmatic approach.

 

With WildFly 18, it is now possible to specify masked passwords as credential passwords and key store passwords for client authentication.

 

This blog post will demonstrate how to generate a masked password using the WildFly PasswordFactory and how to use this masked password as a credential in the authentication client using the two configuration methods.

 

Generating a Masked Password

 

A masked password consists of the following attributes:

 

  • `algorithm` The algorithm used to encrypt the password. If this attribute is not specified, the default value is "masked-MD5-DES".
  • `key-material` The initial key material used to encrypt the password. If this attribute is not specified, the default value is "somearbitrarycrazystringthatdoesnotmatter".
  • `iteration-count` The iteration count used to encrypt the password. This attribute is required.
  • `salt` The salt used to encrypt the password. This attribute is required.
  • `masked-password` The base64 encrypted password (without the "MASK-" prefix).
  • `initialization-vector` The initialization vector used to encrypt the password. This attribute is optional.

 

A list of the supported algorithms can be found at Passwords.

 

For our example, we will be generating a masked-MD5-DES type password. We can generate this password as follows:

 

static final Provider ELYTRON_PROVIDER = new WildFlyElytronProvider();

public static void main(String[] args) throws Exception {

   String algorithm = "masked-MD5-DES";
   char[] keyMaterial = "somearbitrarycrazystringthatdoesnotmatter".toCharArray();
   byte[] salt = "12345678".getBytes();
   int iterationCount = 100;
   String clearPassword = "password1!";
   byte[] initializationVector = null;

   PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm, ELYTRON_PROVIDER);

   MaskedPasswordAlgorithmSpec maskedAlgorithmSpec = new MaskedPasswordAlgorithmSpec(key, iterationCount, salt);
   EncryptablePasswordSpec encryptableSpec = new EncryptablePasswordSpec(clearPassword.toCharArray(), maskedAlgorithmSpec);

   MaskedPassword original = (MaskedPassword) passwordFactory.generatePassword(encryptableSpec);

   MaskedPasswordSpec maskedPasswordSpec = new MaskedPasswordSpec(keyMaterial, iterationCount, salt, masked);
   
   //Get the masked password as a string
   String maskedPassword = ByteIterator.ofBytes(maskedPasswordSpec.getMaskedPasswordBytes()).base64Encode().drainToString();
   System.out.println(String.format("Masked Password: " + maskedPassword));

   //Verify the masked password is the encryption of the clear password
   MaskedPassword restored = (MaskedPassword) passwordFactory.generatePassword(maskedPasswordSpec);
   System.out.println(String.format("Password Verified '%b'", passwordFactory.verify(restored, clearPassword.toCharArray())));

}

When encrypting “password1!” with the attributes defined as above we get the encrypted password “/Nym2s/dssMrabfdIGsZfQ==”.

 

Example Authentication Using Configuration File

 

We can now use the masked password in our client xml configuration. We will need to specify the attributes used to generate the password as well. Because we used the default values for the initializationKeyMaterial and the algorithm, we can simply specify the mandatory attributes:  

 

<configuration>

   <authentication-client xmlns="urn:elytron:client:1.4">

       <authentication-rules>

           <rule use-configuration="masked-config">

               <match-host name="masked"/>

           </rule>

       </authentication-rules>

       <authentication-configurations>

           <configuration name="masked-config">

               <set-user-name name="Guest"/>

               <credentials>

                   <masked-password iteration-count="100" salt="12345678" masked-password="/Nym2s/dssMrabfdIGsZfQ=="/>

               </credentials>

               <sasl-mechanism-selector selector="PLAIN"/>

           </configuration>

       </authentication-configurations>

   </authentication-client>

</configuration>

 

Now when authenticating, a client using this configuration can successfully use the username “User” and the password that was encrypted “password1!”.

 

Example Authentication Using Programmatic Approach

 

We can also create the same configuration programmatically and specify a masked password

 

AuthenticationConfiguration maskedConfig = AuthenticationConfiguration.empty()
                                               .setSaslMechanismSelector(SaslMechanismSelector.NONE.addMechanism(PLAIN))
                                               .useName(“Guest”)
                                               //algorithm, initial key material, and initialization vector are null
                                               .useMaskedPassword("/Nym2s/dssMrabfdIGsZfQ==", null, null, 100, "12345678", null);

AuthenticationContext context = AuthenticationContext.empty();
context = context.with(MatchRule.ALL.matchHost("masked"), maskedConfig);

 

Summary

 

This blog post showed how to generate a masked password. It also showed how to specify a masked password for the authentication credentials using a  client XML configuration and programmatically specifying the configuration.

 

For more information on the authentication client, checkout:

https://github.com/wildfly/wildfly/blob/master/docs/src/main/asciidoc/_client-guide/authentication-client.adoc#authentication-client----wildfly-elytron

The Elytron JDBC SecurityRealm allows users to load identities from databases. These identities can be loaded with multiple credentials and attributes. To define the credentials and attributes, users can use one or more principal queries, each of which can be defined from their own datasource. The JDBC SecurityRealm also supports a variety of password mappers to load representations of a password from a database. Many of these mappers load either hashed or digested representations of the password as well as any related salt from the database.

With WildFly 17, there are now a few new options available for JDBC SecurityRealms. While it was previously assumed that encoded fields were always encoded in base64, there is now the option to specify that the passwords and salts are hex encoded. There is also the option to store passwords, salts, and iteration counts (if applicable) as a single string with the modular crypt password mapper. The modular crypt encoded password can be applied to a number of different password types.

 

In this blog post we will make use of jdbc-security-realm-bcrypt-password project in the elytron-examples repository to demonstrate how to use a JDBC security realm to load a BCrypt password with both hex and base64 encoding, as well as with the modular crypt mapper.

 

https://github.com/wildfly-security-incubator/elytron-examples/tree/master/jdbc-security-realm-bcrypt-password

 

Base64

 

Generating BCrypt Hashed Password and Salt

 

Our first step is to generate a BCrypt salted password using the Wildfly Elytron PasswordFactory.

This will require interacting with the org.wildfly.security.password.PasswordFactory API, which obtains access to implementations from java.security.Provider instances

 

public static void main(String[] args) throws Exception {

     static final Provider ELYTRON_PROVIDER = new WildFlyElytronProvider();
     static final String PASSWORD = "quickstartPwd1!";
     PasswordFactory passwordFactory = PasswordFactory.getInstance(BCryptPassword.ALGORITHM_BCRYPT, ELYTRON_PROVIDER);

     int iterationCount = 10;
     
     byte[] salt = new byte[BCryptPassword.BCRYPT_SALT_SIZE];
     SecureRandom random = new SecureRandom();
     random.nextBytes(salt);

     IteratedSaltedPasswordAlgorithmSpec iteratedAlgorithmSpec = new IteratedSaltedPasswordAlgorithmSpec(iterationCount, salt);
     EncryptablePasswordSpec encryptableSpec = new EncryptablePasswordSpec(PASSWORD.toCharArray(), iteratedAlgorithmSpec);

     BCryptPassword original = (BCryptPassword) passwordFactory.generatePassword(encryptableSpec);
     byte[] hash = original.getHash();
     Base64.Encoder encoder = Base64.getEncoder();
     String encodedHash = encoder.encodeToString(hash);
     String encodedSalt = encoder.encodeToString(salt);

     System.out.println("Encoded Hash = " + encodedHash);
     System.out.println("Encoded Salt = " + encodedSalt);
}

 

 

This will generate a base64 encoded hash of your password and salt which you can store in your database. You should also store your iteration count in the database.


For the jdbc-security-realm-bcrypt-password project, GenerateBCryptPassword is a class provided that generates a BCrypt password. To run this class, you can use the following command from the home directory of the example:

 

$mvn clean install

$mvn exec:java -Dexec.mainClass="org.wildfly.security.examples.GenerateBCryptPassword" -Dexec.args="false false quickstartPwd1!"

 

  • The first argument indicates if the password and salt should be hex encoded
  • The second argument indicates if the password, salt, and iteration count should be modular crypt encoded into a single String
  • The last argument is the clear text password you would like to encrypt.(Warning: if this is run on a shared computer other users can see arguments passed to a running process.)

 

We set hex encoding and modular crypt encoding to false as we would like base64 encoding. We use this command twice to generate hashes for the passwords ‘quickstartPwd1!’ and ‘guestPwd1!’ and load this into our database. The tables used for the example are created in src/main/resources/import.sql. Two parts of this file are worth mentioning:

 

  1. Where we create the table of users to hold an ID, username, password, salt, and iteration count

 

CREATE TABLE USERS (ID INT, USERNAME VARCHAR(20), PASSWORD VARCHAR(60), SALT VARCHAR(60), ITERATION_COUNT INT);

 

  1. Where we add two users and their hashed passwords, salts, and iterations counts that we generated using GenerateBCryptPasswords.
--Username: quickstartUser, Password: quickstartPwd1!
INSERT INTO USERS (ID, USERNAME, PASSWORD, SALT, ITERATION_COUNT) VALUES (1, 'quickstartUser', 'NxHRwFg/YkkRgGUq/D6ARnD+caxlmIg=', 'DMakCMy9DYKBVW/HNrCrPw==',10);
--Username: guest, Password: guestPwd1!
INSERT INTO USERS (ID, USERNAME, PASSWORD, SALT, ITERATION_COUNT) VALUES (2, 'guest', 'sspDe1PYqDdAJ5EeHoRJcXZPuM+vOi8=', 'VO0V0x+j/oZ5r7VvxdzDzw==', 10);

 

Configurations

Once we have generated our hashed passwords and salt, we can load these in using a JDBC security realm and the bcrypt-mapper. Our example provides a configure-server.cli file which has a number of configurations. The section of relevance to us is the following:

 

/subsystem=elytron/jdbc-realm=servlet-security-jdbc-realm:add(
     principal-query=[{sql="SELECT PASSWORD, SALT, ITERATION_COUNT FROM USERS WHERE USERNAME = ?", 
     data-source="ServletSecurityDS",bcrypt-mapper={password-index=1, salt-index=2,iteration-count-index=3}},
     {sql="SELECT R.NAME, 'Roles' FROM USERS_ROLES UR INNER JOIN ROLES R ON R.ID = UR.ROLE_ID INNER JOIN USERS U ON U.ID =UR.USER_ID WHERE U.USERNAME = ?", 
     data-source="ServletSecurityDS",attribute-mapping=[{index=1, to=roles}]}])

 

This configuration adds a JDBC security realm and sets the principal query to select the password, the salt, and the iteration count from the database. We define the principal query with the bcrypt-mapper password mapper with the following attributes:

  • password-index - the column index of our password in the database table
  • salt-index - the column index of our salt in the database table
  • iteration-count-index - the column index of our iteration count in the database table

This command also loads the roles from the database which will determine which login access the user has.

 

Running and Accessing the Application

We’re now ready to run our example and see the results.

 

Step 1: Run the wildfly server

 

${path_to_wildfly}/bin/standalone.sh

 

Step 2: Navigate to the source folder of this example and configure elytron

 

${path_to_wildfly}/bin/jboss-cli.sh --connect --file=configure-server.cli

 

This script will:

    1. Start by adding the JDBC datasource we want to use
    2. Add the JDBC security realm to the elytron subsystem responsible for verifying credentials
    3. Add a role decoder for the "roles" attribute mapping
    4. Adds the servlet-security-quickstart security domain to the elytron subsystem
    5. Adds the HTTP Authentication Factory to the elytron subsystem
    6. Configure Undertow's application security domain

 

Step 3: Build and deploy the artifacts

 

     $mvn clean package install wildfly:deploy

Step 4: Navigate to http://localhost:8080/jdbc-security-realm-bcrypt-password in your browser. You will be prompted to login.

If you login with quickstartUser (password: quickstartPwd1!) you can see that it was successful:

Successfully called Secured Servlet

 

 

Principal : quickstartUser

 

Remote User : quickstartUser

 

Authentication Type : BASIC

If you log in with guest (password: guestPwd1!) you will receive the following error message:

Forbidden

Step 5: To undeploy, navigate to the home directory of the example and run:

${path_to_wildfly}/bin/jboss-cli.sh --connect --file=restore-configurations.cli

 

$mvn clean package wildfly:undeploy

 

Hex

 

Generating BCrypt Hashed Password and Salt

 

The PasswordFactory also provides the capability to store passwords with hex encoding. To implement this we make these changes to our code:

 

public static void main(String[] args) throws Exception {

     static final Provider ELYTRON_PROVIDER = new WildFlyElytronProvider();
     static final String password = "quickstartPwd1!";
     PasswordFactory passwordFactory = PasswordFactory.getInstance(BCryptPassword.ALGORITHM_BCRYPT, ELYTRON_PROVIDER);

     int iterationCount = 10;
     
     byte[] salt = new byte[BCryptPassword.BCRYPT_SALT_SIZE];
     SecureRandom random = new SecureRandom();
     random.nextBytes(salt);
     
     IteratedSaltedPasswordAlgorithmSpec iteratedAlgorithmSpec = new IteratedSaltedPasswordAlgorithmSpec(iterationCount, salt);
     EncryptablePasswordSpec encryptableSpec = new EncryptablePasswordSpec(password.toCharArray(), iteratedAlgorithmSpec);

     BCryptPassword original = (BCryptPassword) passwordFactory.generatePassword(encryptableSpec);
     byte[] hash = original.getHash();
     encodedHash = ByteIterator.ofBytes(hash).hexEncode().drainToString();
     encodedSalt = ByteIterator.ofBytes(salt).hexEncode().drainToString();

     System.out.println("Encoded Hash = " + encodedHash);
     System.out.println("Encoded Salt = " + encodedSalt);
}

 

In the home directory of this example, you can run the following command to use GenerateBCryptPassword to generate a hex encoded password:

 

$mvn clean install

$mvn exec:java -Dexec.mainClass="org.wildfly.security.examples.GenerateBCryptPassword" -Dexec.args="true false quickstartPwd1!"

 

Again, we use this command twice to generate hashes for the passwords ‘quickstartPwd1!’ and ‘guestPwd1!’ and insert this into our database.

 

--Username: quickstartUser, Password: quickstartPwd1!
INSERT INTO USERS (ID, USERNAME, PASSWORD, SALT, ITERATION_COUNT) VALUES (1, 'quickstartUser', '1b16d96be4b63b73d6ec7008f656effefa94ddfa5daa14',’d75ef1b0445dde97315936f8cb62d960',10);

--Username: guest, Password: guestPwd1
INSERT INTO USERS (ID, USERNAME, PASSWORD, SALT, ITERATION_COUNT) VALUES (2, 'guest', 'c4537f43dd62c6c854a7e39378a972bcca8df71810954d', 'a1f8cff8469a6d8203905c38345fc5a3', 10);

 

Configurations

 

The default encoding for bcrypt-mapper is base64. We must now add the attributes password-encoding and salt-encoding to our principal query to indicate hex encoding. All other attributes should remain the same.

 

/subsystem=elytron/jdbc-realm=servlet-security-jdbc-realm:add(
     principal-query=[{sql="SELECT PASSWORD, SALT, ITERATION_COUNT FROM USERS WHERE USERNAME = ?", data-source="ServletSecurityDS",
     bcrypt-mapper={password-index=1, hash-encoding=hex, salt-index=2, salt-encoding=hex, iteration-count-index=3}},
     {sql="SELECT R.NAME, 'Roles' FROM USERS_ROLES UR INNER JOIN ROLES R ON R.ID = UR.ROLE_ID INNER JOIN USERS U ON U.ID = UR.USER_ID WHERE U.USERNAME = ?", 
     data-source="ServletSecurityDS", attribute-mapping=[{index=1, to=roles}]}])

 

Running and Accessing the Application

 

The results for this will be the same as when using base64 encoding.

 

Modular Crypt

 

Generating BCrypt Hashed Password and Salt

 

The PasswordFactory also provides the capability to encode a number of password types using modular crypt which stores the hash, salt, and iteration count as one String.

 

public static void main(String[] args) throws Exception {

     static final Provider ELYTRON_PROVIDER = new WildFlyElytronProvider();
     static final String PASSWORD = "quickstartPwd1!";
     PasswordFactory passwordFactory = PasswordFactory.getInstance(BCryptPassword.ALGORITHM_BCRYPT, ELYTRON_PROVIDER);

     int iterationCount = 10;

     byte[] salt = new byte[BCryptPassword.BCRYPT_SALT_SIZE];
     SecureRandom random = new SecureRandom();
     random.nextBytes(salt);

     IteratedSaltedPasswordAlgorithmSpec iteratedAlgorithmSpec = new IteratedSaltedPasswordAlgorithmSpec(iterationCount, salt);
     EncryptablePasswordSpec encryptableSpec = new EncryptablePasswordSpec(PASSWORD.toCharArray(), iteratedAlgorithmSpec);

     BCryptPassword original = (BCryptPassword) passwordFactory.generatePassword(encryptableSpec);
     String modularCryptString = ModularCrypt.encodeAsString(original);

     System.out.println("Modular Crypt = " + modularCryptString);

}

 

In the example, we can set that we want modular encoding when running GenerateBCryptPassword to generate the password:

 

$mvn clean install

$mvn exec:java -Dexec.mainClass="org.wildfly.security.examples.GenerateBCryptPassword" -Dexec.args="false true quickstartPwd1!"

 

Once done, we must change the database to only hold the password:

 

CREATE TABLE USERS (ID INT, USERNAME VARCHAR(20), PASSWORD VARCHAR(60));

--Username: quickstartUser, Password: quickstartPwd1!
INSERT INTO USERS (ID, USERNAME, PASSWORD) VALUES (1, 'quickstartUser', '$2a$10$dDbUML0NLhCvsBMHr9XiDePgbWxHgsQc/1v2UZ3BUWu0PlRGnrddu');

--Username: guest, Password: guestPwd1!
INSERT INTO USERS (ID, USERNAME, PASSWORD) VALUES (1, 'quickstartUser', '$2a$10$JqTcM8iL.FVNd8O/i28ed.8uDAYRgXkE9EWM176Tz4V0zQz6/ugYq');

 

Configurations

 

We must now change the JDBC security realm configuration to load using the modular-crypt-mapper.

 

/subsystem=elytron/jdbc-realm=servlet-security-jdbc-realm:add(
     principal-query=[{sql="SELECT PASSWORD FROM USERS WHERE USERNAME = ?", 
     data-source="ServletSecurityDS", modular-crypt-mapper={password-index=1}},
     {sql="SELECT R.NAME, 'Roles' FROM USERS_ROLES UR INNER JOIN ROLES R ON R.ID= UR.ROLE_ID INNER JOIN USERS U ON U.ID = UR.USER_ID WHERE U.USERNAME = ?", 
     data-source="ServletSecurityDS", attribute-mapping=[{index=1, to=roles}]}])

 

The only attribute we need to set is the password-index to equal the column index of our password in the table we created.

 

Running and Accessing the Application

 

The results for this will be the same as when using base64 and hex encoding.

 

Summary

This blog has shown how to generate a BCrypt hashed password with Base64 encoding, hex encoding, and modular crypt encoding. We have also seen how to use a JDBC security realm to load a BCrypt password using bcrypt-mapper and to load a modular crypt encoded password using modular-crypt-mapper.

 

For more information on JDBC security realms check out this documentation:

https://github.com/wildfly/wildfly/blob/master/docs/src/main/asciidoc/_elytron/components/JDBC_Security_Realm.adoc

 

For more information on modular crypt encoding check out this documentation:

https://github.com/wildfly/wildfly/blob/master/docs/src/main/asciidoc/_elytron/Passwords.adoc#modular-crypt-encoding

 

For more information on other passwords check out this documentation:

https://github.com/wildfly/wildfly/blob/master/docs/src/main/asciidoc/_elytron/Passwords.adoc