LuciadFusion Platform is a Spring-based application. As such, you can use the Spring framework to extend the existing capabilities of the LuciadFusion server. This article describes how to use Spring to integrate custom authentication logic that overrides the built-in default LuciadFusion Platform security layer.

In this article, we explain how to implement the custom authentication logic through a database. The complete source code of this tutorial is also available as a sample application in the package samples.fusion.platform.security.authentication.
See the Spring Security documentation to learn how to configure application security in a Spring-based application.

Create a YAML-based authentication configuration

The configuration file application-authentication.yml defines security-related properties. It defines database configuration properties, and their corresponding values. The properties from this file will be injected into the CustomDataSourceProperties bean, created in the next step. The configuration properties bean then exposes the properties to the Authentication configuration that you want to plug in. See the sample YAML configuration file:

Expand this section to see the custom security configuration YAML properties file.

Properties: custom security configuration (from samples/fusion/platform/security/application-authentication)
# Custom authentication sample configuration profile #

# Custom authentication profile configuration.
# This profile must be combined with the "fusion.single" & "fusion.development" profiles as active profile. For example by using a vm property:
#   -Dspring.profiles.active=fusion.single,fusion.development,authentication
# or a program argument:
#   --spring.profiles.active=fusion.single,fusion.development,authentication

# This section defines user defined data source properties (used in database authentication).
# Demonstrates configuring a different data source from the one used by LuciadFusion internally.
# This default configuration uses an H2 in-memory database.

  fusion.samples.custom.authentication.datasource:
   # This section defines the location of the sample liquibase changelog configuration file.
   liquibaseChangelogPath: classpath:samples/fusion/platform/security/sample-liquibase-changelog.xml
   # JDBC URL
   url: jdbc:h2:mem:user_db_auth
   # The database username.
   username: luciad
   # The password for the database username.
   password: luciad
   # H2 JDBC driver class.
   driverClassName:  org.h2.Driver
   # Maximum size of database connection pool.
   max-pool-size: 5

Create dedicated Spring property and configuration class for database authentication

Next, you create a CustomDataSourceProperties bean to load the values from the YAML file. This bean must read the externalized configuration properties described in Create a YAML-based authentication configuration and it will be automatically picked up and injected into the Spring Security configuration bean as one of its dependencies.

Add Spring Configuration beans that set up authentication. This example shows how to configure a database for user authentication and authorization. Use Spring framework’s AuthenticationManagerBuilder to set up authentication attributes such as PasswordEncoder and DataSource, and SQL queries to retrieve user names and passwords.

This sample defines two beans that form the core of authentication configuration logic. See the custom datasource properties bean and custom authentication configuration bean for sample CustomDataSourceProperties and CustomAuthenticationConfiguration beans.

Expand this section to view the custom DataSource Properties bean definition.

Program: custom DataSource property configuration (from samples/fusion/platform/security/authentication/config/CustomDataSourceProperties)
/**
 * A user defined data source configuration property source file.
 * Provides access to the externalised data source properties defined
 * in the file {@code application-authentication.yml}.
 */
@Component
@ConfigurationProperties(prefix = "fusion.samples.custom.authentication.datasource")
public class CustomDataSourceProperties {

  private static final ILcdLogger LOGGER = TLcdLoggerFactory.getLogger(CustomDataSourceProperties.class);

  private String fUrl;
  private String fUserName;
  private String fPassword;
  private String fDriverClassName;
  private int fMaxPoolSize;
  private String fLiquibaseChangelogPath;

}

Expand this section to view the custom authentication Configuration bean.

Program: custom authentication configuration(from samples/fusion/platform/security/authentication/config/CustomAuthenticationConfiguration)
/**
 * <p>
 *   A sample spring security configuration class. This configuration class
 *   configures parameters needed to implement and plug in a custom authentication
 *   mechanism overriding Fusion's built-in security configurations. This class also configures
 *   user defined data source to be used for database authentication.
 * </p>
 */
@Configuration
@EnableWebSecurity
@EnableTransactionManagement
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CustomAuthenticationConfiguration {
  private static final ILcdLogger LOGGER = TLcdLoggerFactory.getLogger(CustomAuthenticationConfiguration.class);

  private static final String USERS_BY_USERNAME_QUERY = "select \"username\", \"password\", \"enabled\" from \"users\" where \"username\"=?";
  private static final String AUTHORITIES_BY_USERNAME_QUERY = "select \"username\", \"role\" from \"users\" where \"username\"=?";

  private final CustomDataSourceProperties fCustomDataSourceProperties;

  public CustomAuthenticationConfiguration(CustomDataSourceProperties aCustomDataSourceProperties) {
    fCustomDataSourceProperties = aCustomDataSourceProperties;
  }

  @PostConstruct
  private void init() {
    LOGGER.info("Fusion sample: loading security configuration for database authentication");

  }

  /**
   * Uses AuthenticationManagerBuilder to configure database authentication. If a custom authentication logic is needed
   *  consider implementing {@link org.springframework.security.authentication.AuthenticationProvider}, then register it
   *  using <code>aAuth.authenticationProvider(customAuthenticationProvider);</code>
   */
  @Autowired
  public void configAuthentication(@Lazy AuthenticationManagerBuilder aAuth, @Lazy @Qualifier("userDefinedDataSource") final DataSource aUserDefinedDataSource) throws Exception {
    aAuth.jdbcAuthentication().passwordEncoder(new BCryptPasswordEncoder())
         .dataSource(aUserDefinedDataSource)
         // Spring expects roles to start with ROLE_ by default
         .rolePrefix("ROLE_")
         .usersByUsernameQuery(USERS_BY_USERNAME_QUERY)
         .authoritiesByUsernameQuery(AUTHORITIES_BY_USERNAME_QUERY);
  }

  /**
   * This bean constructor method configures and creates a user-defined bean of
   * type {@link SpringLiquibase}. This bean uses sample liquibase changelog
   * configuration to create a user table and populates it with a default
   * user record to be used for user authentication. The liquibase operations
   * are executed at LuciadFusion server startup.
   *
   * @param aDataSource configured for user authentication via database.
   * @return a {@link SpringLiquibase} bean.
   */
  @Bean
  public SpringLiquibase liquibase(@Qualifier("userDefinedDataSource") final DataSource aDataSource) {
    SpringLiquibase liquibase = new SpringLiquibase();
    liquibase.setChangeLog(fCustomDataSourceProperties.getLiquibaseChangelogPath());
    liquibase.setDataSource(aDataSource);
    return liquibase;
  }

  /**
   *
   * This bean constructor method configures and creates a user-defined bean of
   * type {@link DataSource} to be used for database-based user authentication.
   */
  @Bean
  DataSource userDefinedDataSource() {
    return new HikariDataSource(constructDataSourceConfig());
  }

  /**
   *  Configures a HikariCP based Datasource.
   *  HikariCP is a high-performance JDBC connection pool solution.
   *  A connection pool is a cache of database connections maintained
   *  so that the connections can be reused when future requests to the database are required.
   *  Connection pools may significantly reduce the overall resource usage and improves application
   *  performance.
   *
   */
  private HikariConfig constructDataSourceConfig() {
    HikariConfig hikariConfig = new HikariConfig();
    hikariConfig.setDriverClassName(fCustomDataSourceProperties.getDriverClassName());
    hikariConfig.setJdbcUrl(fCustomDataSourceProperties.getUrl());
    hikariConfig.setUsername(fCustomDataSourceProperties.getUserName());
    hikariConfig.setPassword(fCustomDataSourceProperties.getPassword());

    hikariConfig.setMaximumPoolSize(fCustomDataSourceProperties.getMaxPoolSize());
    hikariConfig.setConnectionTestQuery("SELECT 1");
    hikariConfig.setPoolName("userDefinedHikariCP");

    hikariConfig.addDataSourceProperty("dataSource.cachePrepStmts", "true");
    hikariConfig.addDataSourceProperty("dataSource.prepStmtCacheSize", "250");
    hikariConfig.addDataSourceProperty("dataSource.prepStmtCacheSqlLimit", "2048");
    hikariConfig.addDataSourceProperty("dataSource.useServerPrepStmts", "true");

    return hikariConfig;
  }

  /**
   * This bean constructor method configures and creates a user-defined bean of
   * type {@link PlatformTransactionManager} to be used for transaction management
   * within this sample application.
   */
  @Bean
  public PlatformTransactionManager userDefinedTransactionManager() {
    return new DataSourceTransactionManager(userDefinedDataSource());
  }
}

Make sure that the application can find the custom authentication configuration

You must tell the Spring framework where to find the new user authentication configuration. You can use the fusion.config.additionalScanPackages configuration property to specify extra packages for scanning by the Spring framework. See config/fusion.common.yml configuration file for information about using this configuration property. As a result, you can run the LuciadFusion Platform main class, TLfnFusionPlatformApplication, with this extra property value. The LuciadFusion Platform framework also picks up all Spring components in the new package, or the ones imported by a class in the new package.

Additional Spring Security configuration

Adding a custom HTTP security user authorization configuration is discussed in the Example: adding a Spring HTTP security authorization configuration article.