vaadin / flow

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

Vaadin generated HTML components blocked `401 forbidden` by default Spring Boot security config #17208

Open Alexenon opened 1 year ago

Alexenon commented 1 year ago

Describe your motivation

Vaadin generated HTML elements are blocked due 401 forbitten requests. Does someone know how to allow Vaadin to generate views to allowed pages. For example, "/login" is permitAll() and should be generated without any issues, but for "/" route, user should be authenticated();

Describe the solution you'd like

I'd like to get a template for default Security Config using pure Spring Security, without using VaadinWebSecurity.class.

Additional context

https://stackoverflow.com/questions/76649003/vaadin-doesnt-generate-html-due-security-config/76649082#76649082

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    private static final String LOGIN_URL = "/login";
    private static final String LOGOUT_SUCCESS_URL = "/login";
    private static final String LOGIN_PROCESSING_URL = "/login";
    private static final String LOGIN_FAILURE_URL = "/login?error";

    @Autowired
    private UserService userService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .cors().disable()
                .authorizeHttpRequests(auth -> {
                    auth.requestMatchers("/VAADIN/**", "/PUSH/**", "/UIDL/**").permitAll();
                    auth.requestMatchers("/vaadinServlet/UIDL/**").permitAll();
                    auth.requestMatchers("/vaadinServlet/HEARTBEAT/**").permitAll();
                    auth.requestMatchers("/resources/**").permitAll();
                    auth.requestMatchers("/login").permitAll();
                    auth.requestMatchers("/api/**").authenticated();
                    auth.requestMatchers("/secured").authenticated();
                    auth.requestMatchers("/admin").hasRole("ADMIN");
                    auth.anyRequest().authenticated(); // Commenting this also doesnt help
                })
                .formLogin(loginForm -> {
                    loginForm.loginPage(LOGIN_URL);
                    loginForm.loginProcessingUrl(LOGIN_PROCESSING_URL);
                    loginForm.failureUrl(LOGIN_FAILURE_URL);
                })
                .logout(logout -> logout.logoutSuccessUrl(LOGOUT_SUCCESS_URL))
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .rememberMe().disable()
                .exceptionHandling()
                .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));

        return http.build();
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring()
                .requestMatchers("/resources/**")
                .requestMatchers("/VAADIN/**")
                .requestMatchers("/PUSH/**")
                .requestMatchers("/UIDL/**")
                .requestMatchers("/dev-bundle/**")
                .requestMatchers("/vaadinServlet/**")
                .requestMatchers("/chromewebdata/**")
                .requestMatchers("/images/**")
                .requestMatchers("/icons/**");
    }

    // Rest of beans...
}    
mcollovati commented 1 year ago

@Alexenon thank you for the issue. Can you please elaborate, what do you mean by template for default Security Config using pure Spring Security, without using VaadinWebSecurity?

If you do not want to use VaadinWebSecurity, can't you just pick relevant parts from that class for your own configuration?

Alexenon commented 1 year ago

Using my custom SecurityConfig with default Spring Security, Vaadin elements are blocked to be generated. I'd like to get a list of addresses that web should ignore, so on public pages where no authentication is required, vaadin could generate the frontend side.

What I found is with requestMatchers("/").permitAll() Vaadin components works properly without any issue, but when I change to requestMatchers("/").authenticated(), elements are blocked even for such address /login

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  http
        .csrf().disable()
        .cors().disable()
        .authorizeHttpRequests(auth -> {
            auth
                    .requestMatchers("/login").permitAll()
                    .requestMatchers("/").authenticated() // here is the problem
                    .requestMatchers("/api/**").authenticated() 
                    .requestMatchers("/secured").authenticated()
                    .requestMatchers("/admin").hasRole("ADMIN")
                    .anyRequest().authenticated(); // this line doesnt impact at all
        }

        return http.build();
}

@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
    return (web) -> web.ignoring()
            .requestMatchers("/resources/**")
            .requestMatchers("/VAADIN/**")
            .requestMatchers("/PUSH/**")
            .requestMatchers("/UIDL/**")
            .requestMatchers("/dev-bundle/**")
            .requestMatchers("/vaadinServlet/**")
            .requestMatchers("/chromewebdata/**")
            .requestMatchers("/images/**")
            .requestMatchers("/icons/**")
            .requestMatchers("/manifest.webmanifest")
            .requestMatchers("/sw.js")
            .requestMatchers("/sw-runtime-resources-precache.js")
            .requestMatchers("/offline.html")
            .requestMatchers("/offline-stub.html")
            .requestMatchers("/themes/**")
            .requestMatchers("/favicon.ico");
}

In VaadinWebSecurity.class I also found

public static RequestMatcher getDefaultHttpSecurityPermitMatcher() {
    return getDefaultHttpSecurityPermitMatcher("/*");
}

public static RequestMatcher getDefaultWebSecurityIgnoreMatcher() {
    return getDefaultWebSecurityIgnoreMatcher("/*");
}

When I add this web ignore matcher, or allow this address, all views are now public without any requirement to be authenticated. So now I got a little bit stuck which addresses should be ignored by web.


Basically, I want to be able using Spring Security to set up which URL to be public and which private, and Vaadin to be able to generate components on public addresses without any issues. Hope now my question is more clear

mshabarov commented 1 year ago

@Alexenon thanks for the issue. Could you please explain what do you mean by "Vaadin generated HTML elements" ? Do you mean "http request for a certain navigation target is blocked by Spring Security" ?

Also, could you provide an example project to us and we then can try to setup the desired behaviour ?

Alexenon commented 1 year ago

Hi @mcollovati. So when I try to implement my own Security Configuration without extending VaadinSecurity.class, the Vaadin elements are not displayed at all. There is just a blank page, and in Network section under DevTools in browser (Chrome), I see a 401 exception Failed to load resources.

This Stack Overflow question is a more described version and has additional screenshots