LuciadFusion comes pre-configured with HTTP Security which restricts user access to critical resources within the application. This article describes how to use the Spring Framework to integrate custom HTTP Security logic that overrides the built-in default HTTP Security authorization configuration.

In this article, we explain how to implement the custom HTTP Security authorization logic to restrict user access to critical resources within LuciadFusion. The complete source code of this tutorial is also available as a sample application in the package samples.fusion.platform.security.http.
See the Spring Security documentation to learn how to configure application security in a Spring-based application.

Create a YAML-based authorization configuration

The configuration file application-authorization.yml defines HTTP Security authorization related properties that are used in configuring restrictions on resources from unauthorized access. It stores properties such as the login URL, the logout URL, protected/unprotected endpoint URLs. The properties from this file will be injected into the CustomHttpSecurityEndpointProperties bean, created in the next step.The configuration properties bean then exposes the properties to the HTTP Security authorization configuration that you want to plug in.See the sample YAML configuration file:

Expand this section to see the HTTP Security authorization configuration YAML properties file.

Properties: HTTP Security authorization configuration (from samples/fusion/platform/security/application-authorization)
# Custom authorization sample configuration profile #

# Custom authorization 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,authorization
# or a program argument:
#   --spring.profiles.active=fusion.single,fusion.development,authorization

# This Configuration defines authorization configuration properties picked up by spring security.
fusion.samples.custom.security:
  # Endpoint for logging the user into LuciadFusion server.
  loginUrl: /studio/service/login
  # Endpoint for logging the user out of LuciadFusion server.
  logoutUrl: /studio/service/logout
  # This section defines endpoints that need protection & need authorization.
  authenticatedUrlList:
    # Endpoint used to check whether there is a user currently logged in.
    - /studio/service/api/app-user/current
    # Used to access the specific actuator endpoint.
    - /actuators/configurationInfo
  # This section defines endpoints that are open to access for anyone without authentication/authorization.
  permittedUrlList:
    # Endpoint for obtaining a list of properties that can be used to query Data.
    - /api/data/queryable-properties
    # Endpoint for obtaining a list of Products with an optional search and sort criteria.
    - /api/products
    # Endpoint for obtaining a list of Service types supported by this server.
    - /api/services/types
  # Sample implementation for granting access to actuator endpoints to users having an 'ADMIN' role.
  roleBasedAccessUrlList:
    # Endpoint used to access the available actuators. This endpoint pattern is accessible to authenticated users with 'ADMIN' roles only
    - endpointPattern: /actuators/**
      roles:
        - ADMIN

Create dedicated Spring property and configuration class for HTTP Security authorization

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

The Spring AuthorizationFilter provides authorization for HttpServletRequests. It is inserted into the FilterChainProxy as one of the Security Filters. You can override the default when you define your own SecurityFilterChain bean.

Define a SecurityFilterChain bean to configure HTTP Security using the HttpSecurity class of the Spring framework. This SecurityFilterChain is executed in addition to LuciadFusion’s SecurityFilterChain. This section defines rules that affect how your application handles incoming HTTP requests. You can configure attributes such as URL patterns, form-based authentication properties such as login/logout URLs, and you can restrict access to a resource.

Expand this section to view the HTTP Security Properties bean definition.

Program: HTTP Security property (from samples/fusion/platform/security/http/config/CustomHttpSecurityEndpointProperties)
/**
 * A bean class exposing the properties defined in {@code application-authorization.yml}.
 * The properties are injected into the fields of this class by the spring container.
 */
@Component
@ConfigurationProperties(prefix = "fusion.samples.custom.security")
public class CustomHttpSecurityEndpointProperties {

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

  private String fLoginUrl;
  private String fLogoutUrl;
  private List<String> fAuthenticatedUrlList;
  private List<String> fPermittedUrlList;
  private String fLoginPage;
  private Set<RoleBasedAccessibleEndpoints> fRoleBasedAccessUrlList;


  public static final class RoleBasedAccessibleEndpoints {
    private String fEndpointPattern;
    private Set<String> fRoles;

    public String getEndpointPattern() {
      return fEndpointPattern;
    }

    public void setEndpointPattern(String aEndpointPattern) {
      fEndpointPattern = aEndpointPattern;
    }

    public Set<String> getRoles() {
      return fRoles;
    }

    public void setRoles(Set<String> aRoles) {
      fRoles = aRoles;
    }

    @Override
    public boolean equals(Object aO) {
      if (this == aO) {
        return true;
      }
      if (aO == null || getClass() != aO.getClass()) {
        return false;
      }
      RoleBasedAccessibleEndpoints that = (RoleBasedAccessibleEndpoints) aO;
      return Objects.equals(fEndpointPattern, that.fEndpointPattern) && Objects.equals(fRoles, that.fRoles);
    }

    @Override
    public int hashCode() {
      return Objects.hash(fEndpointPattern, fRoles);
    }
  }
}

Expand this section to see the HTTP Security configuration.

Snippet: HTTP Security configuration (from samples/fusion/platform/security/http/config/CustomHttpSecurityConfiguration)
AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry endpointConfigurer = aHttp
    .headers().cacheControl().disable()
    .and()
    // Spring security kicks in when it intercepts an incoming request matching the pattern "/**".
    .securityMatcher("/**")
    // Restricts open access and force mandatory authentication
    .authorizeHttpRequests()
    // URL list allowed to be accessed by anyone without needing to authenticate.
    .requestMatchers(fSecurityProperties.getPermittedUrlList()
                                        .stream()
                                        .map(aBuilder::pattern)
                                        .toArray(MvcRequestMatcher[]::new))
    .permitAll()
    // URL list allowed to be accessed by authenticated users.
    .requestMatchers(fSecurityProperties.getAuthenticatedUrlList()
                                        .stream()
                                        .map(aBuilder::pattern)
                                        .toArray(MvcRequestMatcher[]::new))
    .authenticated();
// Grant access to actuator endpoints to users having an 'ADMIN' role.
for (CustomHttpSecurityEndpointProperties.RoleBasedAccessibleEndpoints roleBasedAccessibleEndpoints : fSecurityProperties.getRoleBasedAccessUrlList()) {
  endpointConfigurer.requestMatchers(aBuilder.pattern(roleBasedAccessibleEndpoints.getEndpointPattern()))
                    .hasAnyRole(roleBasedAccessibleEndpoints.getRoles().toArray(new String[0]));
}
// Enables user login by accepting username and password provided through an HTML form.
endpointConfigurer
    .and()
    // Specifies including the Form based authentication support and provide associated configuration parameters.
    .formLogin()
    .loginProcessingUrl(fSecurityProperties.getLoginUrl())
    .successHandler(successHandler)
    .permitAll()
    .and()
    // Specifies including the logout support and provide associated configuration parameters like logout url, session invalidation etc.
    .logout()
    .invalidateHttpSession(true)
    .logoutUrl(fSecurityProperties.getLogoutUrl())
    .permitAll()
    .and()
    // Configures the CORS filter and associated parameters.
    .cors().configurationSource(getCorsConfigurationSource())
    .and()
    // Adds CSRF support by default. Needs to be explicitly disabled.
    .csrf().disable();

Full sample code for the HTTP Security authorization configuration

Expand this section to see the full sample.

Program: Spring HTTP Security configuration (from samples/fusion/platform/security/http/config/CustomHttpSecurityConfiguration)
/**
 * <p>
 * A sample spring security configuration class. This configuration class configures parameters needed to implement
 * security of Endpoint Urls exposed by LuciadFusion Studio.
 * </p>
 */
@Configuration
@EnableWebSecurity
public class CustomHttpSecurityConfiguration {
  private static final ILcdLogger LOGGER = TLcdLoggerFactory.getLogger(CustomHttpSecurityConfiguration.class);

  private final CustomHttpSecurityEndpointProperties fSecurityProperties;

  public CustomHttpSecurityConfiguration(CustomHttpSecurityEndpointProperties aSecurityProperties) {
    fSecurityProperties = aSecurityProperties;
  }

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

  @Bean
  public MvcRequestMatcher.Builder mvcRequestBuilder(HandlerMappingIntrospector aIntrospector) {
    return new MvcRequestMatcher.Builder(aIntrospector);
  }

  /**
   * This method configures {@link HttpSecurity}
   *
   * @param aHttp the {@link HttpSecurity} to be configured.
   * @throws Exception
   */
  @Bean
  @Order(1)
  @Autowired
  public SecurityFilterChain fusionFilterChain(HttpSecurity aHttp, MvcRequestMatcher.Builder aBuilder) throws Exception {
    SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
    successHandler.setTargetUrlParameter("redirect");

    AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry endpointConfigurer = aHttp
        .headers().cacheControl().disable()
        .and()
        // Spring security kicks in when it intercepts an incoming request matching the pattern "/**".
        .securityMatcher("/**")
        // Restricts open access and force mandatory authentication
        .authorizeHttpRequests()
        // URL list allowed to be accessed by anyone without needing to authenticate.
        .requestMatchers(fSecurityProperties.getPermittedUrlList()
                                            .stream()
                                            .map(aBuilder::pattern)
                                            .toArray(MvcRequestMatcher[]::new))
        .permitAll()
        // URL list allowed to be accessed by authenticated users.
        .requestMatchers(fSecurityProperties.getAuthenticatedUrlList()
                                            .stream()
                                            .map(aBuilder::pattern)
                                            .toArray(MvcRequestMatcher[]::new))
        .authenticated();
    // Grant access to actuator endpoints to users having an 'ADMIN' role.
    for (CustomHttpSecurityEndpointProperties.RoleBasedAccessibleEndpoints roleBasedAccessibleEndpoints : fSecurityProperties.getRoleBasedAccessUrlList()) {
      endpointConfigurer.requestMatchers(aBuilder.pattern(roleBasedAccessibleEndpoints.getEndpointPattern()))
                        .hasAnyRole(roleBasedAccessibleEndpoints.getRoles().toArray(new String[0]));
    }
    // Enables user login by accepting username and password provided through an HTML form.
    endpointConfigurer
        .and()
        // Specifies including the Form based authentication support and provide associated configuration parameters.
        .formLogin()
        .loginProcessingUrl(fSecurityProperties.getLoginUrl())
        .successHandler(successHandler)
        .permitAll()
        .and()
        // Specifies including the logout support and provide associated configuration parameters like logout url, session invalidation etc.
        .logout()
        .invalidateHttpSession(true)
        .logoutUrl(fSecurityProperties.getLogoutUrl())
        .permitAll()
        .and()
        // Configures the CORS filter and associated parameters.
        .cors().configurationSource(getCorsConfigurationSource())
        .and()
        // Adds CSRF support by default. Needs to be explicitly disabled.
        .csrf().disable();
    return aHttp.build();
  }

  private static UrlBasedCorsConfigurationSource getCorsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedOrigins(Collections.singletonList("*"));
    configuration.setAllowedMethods(Arrays.asList("GET", "PUT", "POST", "PATCH", "DELETE", "OPTIONS"));
    configuration.addAllowedHeader("*");
    configuration.setAllowCredentials(true);
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
  }
}

Make sure that the application can find the HTTP Security configuration

You must tell the Spring framework where to find the new HTTP Security 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.