vaadin / flow

Vaadin Flow is a Java framework binding Vaadin web components to Java. This is part of Vaadin 10+.
Apache License 2.0
601 stars 165 forks source link

Vaadin Flow 24.3.3 - Mixed Content #18737

Closed dbMe1 closed 6 months ago

dbMe1 commented 6 months ago

Description of the bug

When I run my app locally (http) it works fine. When I deploy it to production (https) and access the Login Screen via https, the browser console says:

Mixed Content: The page at 'https://dbme.app/sw.js' was loaded over HTTPS, but requested an insecure resource 'http://dbme.app/login'. This request has been blocked; the content must be served over HTTPS.

sw.js:1 Uncaught (in promise) TypeError: Failed to fetch
    at X.fetch (sw.js:1:3960)
    at d._handleInstall (sw.js:1:8482)
    at d._handle (sw.js:1:7953)
    at async d._getResponse (sw.js:1:7010)

a portion of my class:

@SpringComponent
@UIScope
@Route("/login")
@PageTitle("Login")
@AnonymousAllowed
public class LoginView extends VerticalLayout implements BeforeEnterObserver {

Do I need to adjust a vaadin configuration?

My app runs on JDK 21 + Spring Boot 3.2.2

Thanks for any help.

Expected behavior

I would expect to not see this error

Minimal reproducible example

it's just a basic Login view as given in the docs

Versions

mcollovati commented 6 months ago

It looks like something in your code is doing a redirect to http://dbme.app/login when trying to access a protected resource. One of this request (to the root path) is initiated from the service worker, but I can see the same when Flow client is trying to load https://dbme.app/js/functions.js.

What does your Spring Security configuration look like? Are you extending from VaadinWebSecurity or using a completely custom config?

mcollovati commented 6 months ago

Also, if the application is behind a reverse proxy, double check that both sides are configured to handle X-Forwarded headers

dbMe1 commented 6 months ago

I will check the X-Forwarded headers as u say, Thanks. In the meantime, here is my Spring Security Config:

import com.vaadin.flow.spring.security.VaadinWebSecurity;
import java.util.Collection;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import us.me2tech.dbme.app.view.LoginView;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends VaadinWebSecurity {

  /**
   * paths that are ignored by Vaadin
   */
  //@formatter:off
  private static final Collection<String> IGNORED_PATHS = List.of(
       "/api/**"
      ,"/swagger-ui/**"
      ,"/swagger-ui.html"
      ,"/html/**"
      ,"/js/**"
      ,"/actuator"
      ,"/actuator/**"
  );
  //@formatter:on

  //private static final String[] IGNORED_PATHS_ARRAY = IGNORED_PATHS.toArray(new String[0]);

  @Bean
  public PasswordEncoder passwordEncoder() {
    return PasswordEncoderFactories.createDelegatingPasswordEncoder();
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {

    http
        .authorizeHttpRequests(req -> {
          //req
          //.requestMatchers(IGNORED_PATHS.toArray(new String[0]))
          //.permitAll()

          for (String path : IGNORED_PATHS) {
            req.requestMatchers((AntPathRequestMatcher.antMatcher(path))).permitAll();
          }
        });

    //so that POST requests to endpoints will work
    http.csrf(csrf -> {
      //csrf.ignoringRequestMatchers(IGNORED_PATHS_ARRAY);
      for (String path : IGNORED_PATHS) {
        csrf.ignoringRequestMatchers((AntPathRequestMatcher.antMatcher(path)));
      }
    });

    super.configure(http);
    setLoginView(http, LoginView.class);
  }

  @Override
  public void configure(WebSecurity web) throws Exception {
    super.configure(web);
    //web.ignoring().requestMatchers(VAADIN_IGNORED_PATHS.toArray(new String[0]));

  }

  /**
   * Allows access to static resources, bypassing Spring security.
   */
  //@Override
  public void configureDISABLED(WebSecurity web) throws Exception {

    web.ignoring().requestMatchers(//.antMatchers(
        // Vaadin Flow static resources //
        "/VAADIN/**",

        // the standard favicon URI
        "**/favicon.ico", "**/favicon.icon",

        // icons and images
        "/icons/**", "/images/**",

        // (development mode) H2 debugging console
        "/h2-console/**",

        // the robots exclusion standard
        "/robots.txt",

        // web application manifest //
        "/manifest.webmanifest", "/sw.js", "/offline-page.html",

        // (development mode) static resources //
        "/frontend/**",

        // (development mode) webjars //
        "/webjars/**",

        // (production mode) static resources //
        "/frontend-es5/**", "/frontend-es6/**");

    super.configure(web);
  }

}
dbMe1 commented 6 months ago

You had mentioned the idea of something in my code doing a redirect. I have not written any such code but do you suppose that there could be some Vaadin or Spring-Security configuration that is doing it?

Also, regarding the X-Forwarded headers I got this response from the company that is hosting my app (fly.io):

image

Also I can add the following (which I also told fly.io):

=== When I run my app locally (http) it works fine. When I run it on Railway (https) it works fine. When I run it on fly.io (http) it works fine until I enable SSL on fly.io, then the issue comes.

mcollovati commented 6 months ago

The redirect to login may be caused by Spring Security or Vaadin access control, but they both should be able to handle the correct protocol when x-forward headers are present.

And, as you said, the configuration works on one of the service providers.

I would check the header values, as suggested by fly.io team, and also set spring security logs to debug level. Maybe it can provide some additional information

dbMe1 commented 6 months ago

Is there anything in the Vaadin login mechanism that would cause this sw,js (served via httpS) to then request the login screen via http?

mcollovati commented 6 months ago

Nothing comes to my mind right now. But you can try to remove the PWA annotation from your AppShellConfigurator class and see what happens.

mcollovati commented 6 months ago

Do you have some particular configuration in application.properties?

dbMe1 commented 6 months ago

I will try removal of @PWA and update here. As for app props... lots of commented-out stuff (sorry) but here it is in case u see something, thank you:

server.port=${PORT:80}
spring.application.name=dbME
spring.jackson.serialization.write-dates-as-timestamps=false
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=UTC
#need this to fix startup failure that was happening when using real Mongo
#(not in-memory), and only if Collections already existed in Mongo (which made little sense)
spring.main.allow-circular-references=true
spring.main.allow-bean-definition-overriding=true
app.support.email=support@dbME.us
server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=10s
#-----------------------------
# Actuator Endpoints over HTTP
#-----------------------------
management.endpoints.web.exposure.include=*
management.info.git.mode=full
management.info.env.enabled=true
management.endpoint.health.show-details=always
management.endpoint.health.probes.enabled=true
management.endpoint.health.group.liveness.include=livenessState
management.endpoint.health.group.readiness.include=readinessState
#-------------------------------
#
#-------------------------------
#security.basic.enabled=false
#vaadin.urlMapping=/ui/*
#vaadin.excludeUrls=/api/**
#disable Swagger PetStore
springdoc.swagger-ui.disable-swagger-default-url=true
spring.h2.console.enabled=false
#spring.jpa.hibernate.ddl-auto=update
#spring.datasource.url=jdbc:h2:mem:parts
#show the queries (Mongo):
logging.level.org.springframework.data.mongodb.core.MongoTemplate=INFO
# Default logging detail level for all instances of SimpleLogger.
# Must be one of ("trace", "debug", "info", "warn", or "error").
# If not specified, defaults to "info".
logging.level.root=info
vaadin.servlet.productionMode=true
#vaadin.whitelisted-packages = com.vaadin,org.vaadin,dev.hilla,com.flowingcode
spring.servlet.multipart.max-file-size=5MB
spring.servlet.multipart.max-request-size=5MB
spring.servlet.multipart.enabled=true
#default is 2k
spring.data.web.pageable.max-page-size=5000
#------------------------------
#---- SECRETS -----------------
#------------------------------
recaptcha.secret.key=${RECAPTCHA_SECRET_KEY}
mailerSend.api.key=${MAILERSEND_API_KEY}
telnyx.api.key=${TELNYX_API_KEY}
spring.data.mongodb.uri=${SPRING_DATA_MONGO_URI}
app.stripe.secret.key.test=${STRIPE_SECRET_KEY_TEST}
app.stripe.secret.key.prod=${STRIPE_SECRET_KEY_PROD}
app.stripe.secret.key.webhooks.test=${STRIPE_SECRET_KEY_WEBHOOKS_TEST}
app.stripe.secret.key.webhooks.prod=${STRIPE_SECRET_KEY_WEBHOOKS_PROD}
pwd.site.owner=${PASSWORD_SITE_OWNER}
app.payments.mode=${APP_PAYMENTS_MODE:TEST}
dbMe1 commented 6 months ago

Removing PWA made no difference. Thanks anyway.

dbMe1 commented 6 months ago

here is a new clue.. I see even Vaadin's heartbeat request (made via httpS) is receiving a 302 redirect to the login screen.

image

mcollovati commented 6 months ago

It definitely looks like there's something wrong in the security configuration

mcollovati commented 6 months ago

But you said the same config works correctly with http

dbMe1 commented 6 months ago

ok I did more simplifications on my LoginView (moved reCAPTCHA checks out of the view) and now, while I still see the Mixed Content error, I am able to login successfully. I think the MixedContent error was a red herring, and the real issue was with the reCAPTCHA javascript not being found, and causing a (different) error in the browser console about it (that error would come after the MixedContent error) which was a bit misleading to me.

I had even turned on a "force httpS by redirecting all http requests to httpS" feature in Cloudfare proxy (was not proxying when I had originally reported this issue). The proxying and that setting seemed to make no diff.

What seemed to fix it was (further) simplifying my LoginView.

Thanks for the help.

mcollovati commented 6 months ago

I'm glad you resolved the problem. I'll close the ticket.