Static resources problem:

Hi everyone!

I have a problem with getting resources(e.g: images) from static folder without login. That is to say, I put image into META-INF/resources to load in login view, but it redirects to login page. As known as META-INF/resources is automatically added to static folder by Spring Boot.
How can I fix this?

1 Like

Hi @abdulmaliknajmiddino

Creating /src/main/resources/META-INF/resources/jmix-logo.png file and adding <label id="imageLab" htmlEnabled="true" value="&lt;img src='/jmix-logo.png'&gt;"/> to the login screen works for me.

Could you provide a test project demonstrating the problem?

Thanks for your response.

Actually we use Flow UI template in Jmix. There isn’t any 'htmlEnabled' or 'value' attributes of <label/>. In this template, we don’t have an access to /src/main/resources/META-INF/resources/ folder in login page despite this folder already exists by default and having access after authorization. Can you provide again how to get resources statically in Flow UI template?

Create a new Flow UI project and add the image component to the login view:

<view xmlns="http://jmix.io/schema/flowui/view"
      focusComponent="login"
      title="msg://LoginView.title">
    <layout justifyContent="CENTER" alignItems="CENTER" classNames="jmix-login-main-layout">
        <image resource="/icons/icon.png" height="5em" width="5em"/>
        <loginForm id="login" ...

It will display the META-INF/resources/icons/icon.png which is present in the project template:

image

:sweat_smile: Unfortunately only this default icon works with this name (icon.png), images with other names doesn’t work. I have tried multiple times and found
image. Default icon provided with its direction. It seems we have to add such kind of configuration to define custom added images, right?

You are right, it turned out that only this resource is currently permitted by security configuration.

We’ll add a publicly available folder by default in a future release. Now you can enable any folder by overriding the security configuration in your project. For example, if you create the following class:

import io.jmix.securityflowui.FlowuiSecurityConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@EnableWebSecurity
@Configuration
public class MySecurityConfiguration extends FlowuiSecurityConfiguration {

    @Override
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/img/**").permitAll();
        return super.securityFilterChain(http);
    }
}

you will be able to place an image to src/main/resources/public/img folder and use it on the login view:

<image resource="img/Homer-Simpson.png" height="10em" width="5em"/>
1 Like

Hi Konstantin
I was trying to use logo in my FlowUI application and this post helped, thank you.

how can I align the image to the middle?

1 Like

Hi Konstantin.
I need to address static resources (images in /icons/ folder).
I’m trying to translate the SecurityFilterChain method of your example to FlowUI 2.0 because of deprecations.
I modified the method like this:
imagen

The application compiles OK:
imagen

The problem is that when I run the application the following error appears:
java.lang.IllegalStateException: Authentication is not set. Use SystemAuthenticator in non-user requests like schedulers or asynchronous calls.

I really don’t know how to implement SystemAuthenticator

TIA

German Turriziani

Hi,

By overriding the FlowuiSecurityConfiguration provided by Jmix and doing http.securityMatcher("/icons/**") there you break the Jmix configuration because it stops processing requests to all other URLs.

You need register a new SpringSecurityFilterChain bean and set the order for it less than the order of default Jmix FlowUI security filter chain. For example:

@Configuration
public class MySecurityConfiguration  {

    @Bean
    @Order(JmixSecurityFilterChainOrder.FLOWUI - 10)
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.securityMatcher("/icons/**")
                .authorizeHttpRequests((authorize) -> authorize.requestMatchers("/icons/**").permitAll());
        return http.build();
    }
}
1 Like

Thak you very much for you help Maxim

This works great for Spring Websocket.

But when i try this for my Servlet it redirects me to login

package de.bytestore.hostinger.api;

import com.google.gson.Gson;
import de.bytestore.hostinger.gson.templates.JSONStatus;
import de.bytestore.hostinger.gson.templates.JSONStatusMessage;
import de.bytestore.hostinger.handler.PaymentHandler;
import io.jmix.core.JmixSecurityFilterChainOrder;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.util.UriTemplate;

import java.io.IOException;

/**
 * Configuration class for the PaymentAPIRoute servlet.
 */
@Configuration
@WebServlet(value = "/api/payment/{provider}", loadOnStartup = 1)
public class PaymentAPIRoute extends HttpServlet {
    protected static final Logger log = org.slf4j.LoggerFactory.getLogger(PaymentAPIRoute.class);

    @Bean(name = "paymentSecurityFilterChain")
    @Order(JmixSecurityFilterChainOrder.FLOWUI - 10)
    // https://forum.jmix.io/t/static-resources-problem/2351/10
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.securityMatcher("/api/payment/**")
                .authorizeHttpRequests((authorize) -> authorize.requestMatchers("/api/payment/**").permitAll());
        return http.build();
    }

    @Bean
    public ServletRegistrationBean exampleServletBean() {
        ServletRegistrationBean bean = new ServletRegistrationBean(
                this, "/api/payment/*");
        bean.setLoadOnStartup(1);
        return bean;
    }

    /**
     * Handles a POST request to authorize a payment with a specified provider.
     *
     * @param requestIO  the HTTP servlet request object
     * @param responseIO the HTTP servlet response object
     * @throws ServletException if a servlet-specific error occurs during this request
     * @throws IOException      if an I/O error occurs during this request
     */
    @Override
    protected void doPost(HttpServletRequest requestIO, HttpServletResponse responseIO) throws ServletException, IOException {
        this.handleRequest(requestIO, responseIO);
    }

    /**
     * Handles a request for payment authorization with a specified provider.
     *
     * @param requestIO  the HTTP servlet request object
     * @param responseIO the HTTP servlet response object
     */
    private void handleRequest(HttpServletRequest requestIO, HttpServletResponse responseIO) {
        String providerIO = this.extractProviderFromUri(requestIO.getRequestURI());
        try {
            if(providerIO != null  && !providerIO.isEmpty()) {
                    boolean stateIO = PaymentHandler.authorize(requestIO, responseIO,  providerIO);

                    if(!stateIO) {
                        this.notFound(responseIO, providerIO);
                    }  else  {
                        log.info("Handled Payment Webhook for Provider '" + providerIO + "'.");
                    }
            }  else {
                this.notFound(responseIO, providerIO);
            }
        } catch (Exception exceptionIO) {
            log.error("Unable to execute Payment Provider IPN Function for '" + providerIO + "'.", exceptionIO);
        }
    }

    /**
     * Sets the HTTP response status as internal server error and writes a JSON status message indicating that the authorize provider for the given provider name cannot be found.
     *
     * @param responseIO the HTTP servlet response object
     * @param providerIO the name of the authorize provider
     * @throws IOException if an I/O error occurs during this request
     */
    private void notFound(HttpServletResponse responseIO, String providerIO) throws IOException {
        responseIO.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        responseIO.getWriter().write(new Gson().toJson(new JSONStatusMessage(JSONStatus.ERROR, "Unable to find authorize provider for '" + providerIO + "'.")));
    }

    /**
     * Handles a GET request to authorize a payment with a specified provider.
     *
     * @param requestIO  the HTTP servlet request object
     * @param responseIO the HTTP servlet response object
     * @throws ServletException if a servlet-specific error occurs during this request
     */
    @Override
    protected void doGet(HttpServletRequest requestIO, HttpServletResponse responseIO) throws ServletException, IOException {
        this.handleRequest(requestIO, responseIO);
        //        responseIO.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
        //        responseIO.getWriter().write(new Gson().toJson(new JSONStatusMessage(JSONStatus.ERROR, "Please use POST Method.")));
    }



    /**
     * Extracts the provider name from the given URI.
     *
     * @param uri The URI string from which to get the provider name
     * @return The provider name extracted from the URI
     */
    private String extractProviderFromUri(String uri) {
        UriTemplate template = new UriTemplate("/api/payment/{provider}");
        return template.match(uri).get("provider");
    }
}

18:48:11.445 [http-nio-80-exec-3] INFO c.v.flow.spring.security.RequestUtil - Navigation Access Control is disabled. Cannot determine if api/payment/paypal refers to a public view, thus access is denied. Please add an explicit request matcher rule for this URL.

Same problem, did you found a solution?

I found out that following is semi working, after applying this, my HTTP Servlet ist working but JMIX fails…

http.securityMatcher((authorize) -> authorize.re("/api/payment/**").permitAll());

Unfortunately, I have not yet found a solution for HTTPServlets, and only GET requests are possible because no CRSF token is passed…

Does anyone here have any other ideas, is there something similar to HTTPServlets?

image

I have also tried to switch off CRSF, but apparently this is not recognized / overwritten.

Discussed here: HTTPServlet POST/PUT... with no Session

1 Like