V24.9 VaadinWebSecurity depreacted

Since 24.9 VaadinWebSecurity is deprecated and will be replaces in 24.9.

Do we need to switch to FilterChain? Does it then support stateless login? Looks like it needs deep changes :face_with_spiral_eyes:

    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
//ToDo
}

VaadinWebSecurity will be removed in Vaadin 25.

The migration should not be so difficult; the new VaadinSecurityConfigurer provides basically the same feature that you can activate with VaadinWebSecurity but it is also more flexible.

Here’s a simple example

Before

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends VaadinWebSecurity {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(registry -> {
            registry.requestMatchers("/assets/**").permitAll();
        });
        super.configure(http);
        setLoginView(http, "/login", "/");
    }
}

After

@Configuration
@EnableWebSecurity
@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class)
public class SecurityConfig {

    @Bean
    public SecurityFilterChain vaadinSecurityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(registry -> {
            registry.requestMatchers("/assets/**").permitAll();
        });
        http.with(vaadin(), vaadin -> vaadin.loginView("/login", "/"));
        return http.build()
    }
}

For stateless login, you need to use VaadinStatelessSecurityConfigurer (that is already used under the hood by VaadinWebSecurity)

Before

    @Override
    protected void configure(HttpSecurity web) throws Exception {
        ...
        setStatelessAuthentication(http,
                new SecretKeySpec(Base64.getDecoder().decode(authSecret),
                        JwsAlgorithms.HS256),
                "com.example.application");        
        ...
    }

After

    @Bean
    public SecurityFilterChain vaadinSecurityFilterChain(HttpSecurity http) throws Exception {
        ...
        http.with(new VaadinStatelessSecurityConfigurer<>(),
            stateless -> stateless.issuer("com.example.application")
                .withSecretKey()
                .secretKey(
                	new SecretKeySpec(
                        Base64.getDecoder().decode(authSecret),
                    	JwsAlgorithms.HS256)
                    )
                )
		...
    }

Ah okay Thank you, that information i was missing :)

Just for reference for others, I just upgraded my app and here is the complete before / after.

Before:

package ai.goacquire.frontend.config;

import ai.goacquire.frontend.security.CustomAuthenticationFailureHandler;
import ai.goacquire.frontend.security.CustomUserAuthoritiesMapper;
import ai.goacquire.frontend.security.OAuth2LoginSuccessHandler;
import com.vaadin.flow.spring.security.VaadinWebSecurity;
import com.vaadin.hilla.route.RouteUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.security.authorization.AuthorityAuthorizationManager;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;

import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig extends VaadinWebSecurity {
    protected final Log logger = LogFactory.getLog(this.getClass());
    private final RouteUtil routeUtil;
    private final OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler;
    private final CustomUserAuthoritiesMapper customUserAuthoritiesMapper;
    private final CustomAuthenticationFailureHandler customAuthenticationFailureHandler;
    @Value("${auth.secret}")
    private String authSecret;

    public SecurityConfig(RouteUtil routeUtil,
                          OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler,
                          CustomUserAuthoritiesMapper customUserAuthoritiesMapper,
                          CustomAuthenticationFailureHandler customAuthenticationFailureHandler) {
        this.routeUtil = routeUtil;
        this.oAuth2LoginSuccessHandler = oAuth2LoginSuccessHandler;
        this.customUserAuthoritiesMapper = customUserAuthoritiesMapper;
        this.customAuthenticationFailureHandler = customAuthenticationFailureHandler;
    }


    /**
     * Protects ALL methods (including inherited) in admin services - requires ADMIN role
     * Using bean pattern which handles inheritance properly
     */
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    static Advisor protectAdminServices() {
        AspectJExpressionPointcut pattern = new AspectJExpressionPointcut();
        pattern.setExpression(
                "bean(destinationService) || " +
                        "bean(sourceService) || " +
                        "bean(sourceAuthConfigService) || " +
                        "bean(userService) || " +
                        "bean(roleService)"
        );
        return new AuthorizationManagerBeforeMethodInterceptor(pattern,
                AuthorityAuthorizationManager.hasRole("ADMIN"));
    }

    /**
     * Protects ALL methods (including inherited) in user services - requires USER role or higher
     */
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    static Advisor protectUserServices() {
        AspectJExpressionPointcut pattern = new AspectJExpressionPointcut();
        pattern.setExpression("bean(prefectCredentialService)");
        return new AuthorizationManagerBeforeMethodInterceptor(pattern,
                AuthorityAuthorizationManager.hasAnyRole("USER", "ADMIN", "INTERNAL"));
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // First set up all the OAuth and security related paths
        http.authorizeHttpRequests(registry -> registry
                        .requestMatchers(routeUtil::isRouteAllowed).permitAll()
                        .requestMatchers("/login").permitAll()
                        .requestMatchers("/login/oauth2/authorization/microsoft").permitAll()
                        .requestMatchers("/login/oauth2/authorization/google").permitAll()
                        .requestMatchers("/access-denied").permitAll()
                        .requestMatchers("/error").permitAll()
                )
                // Configure OAuth2 login with custom user authorities mapper
                .oauth2Login(oauth2 -> oauth2
                        .loginPage("/login")
                        .successHandler(oAuth2LoginSuccessHandler)
                        .failureHandler(customAuthenticationFailureHandler)
                        .userInfoEndpoint(userInfo -> userInfo
                                .userAuthoritiesMapper(customUserAuthoritiesMapper)
                        )
                );

        // Default config to allow Vaadin paths
        super.configure(http);

        http.sessionManagement(httpSecuritySessionManagementConfigurer ->
                httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

        setLoginView(http, "/login");

        // Enable stateless authentication
        setStatelessAuthentication(http,
                new SecretKeySpec(Base64.getDecoder().decode(authSecret), JwsAlgorithms.HS256)
                , "ai.goacquire.frontend"
                , 43200);
    }
}

After:

package ai.goacquire.frontend.config;

import ai.goacquire.frontend.security.CustomAuthenticationFailureHandler;
import ai.goacquire.frontend.security.CustomUserAuthoritiesMapper;
import ai.goacquire.frontend.security.OAuth2LoginSuccessHandler;
import com.vaadin.flow.spring.security.VaadinAwareSecurityContextHolderStrategyConfiguration;
import com.vaadin.flow.spring.security.stateless.VaadinStatelessSecurityConfigurer;
import com.vaadin.hilla.route.RouteUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Role;
import org.springframework.security.authorization.AuthorityAuthorizationManager;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
import org.springframework.security.web.SecurityFilterChain;

import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

import static com.vaadin.flow.spring.security.VaadinSecurityConfigurer.vaadin;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class)
public class SecurityConfig {
    protected final Log logger = LogFactory.getLog(this.getClass());
    private final RouteUtil routeUtil;
    private final OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler;
    private final CustomUserAuthoritiesMapper customUserAuthoritiesMapper;
    private final CustomAuthenticationFailureHandler customAuthenticationFailureHandler;
    @Value("${auth.secret}")
    private String authSecret;

    public SecurityConfig(RouteUtil routeUtil,
                          OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler,
                          CustomUserAuthoritiesMapper customUserAuthoritiesMapper,
                          CustomAuthenticationFailureHandler customAuthenticationFailureHandler) {
        this.routeUtil = routeUtil;
        this.oAuth2LoginSuccessHandler = oAuth2LoginSuccessHandler;
        this.customUserAuthoritiesMapper = customUserAuthoritiesMapper;
        this.customAuthenticationFailureHandler = customAuthenticationFailureHandler;
    }

    /**
     * Protects ALL methods (including inherited) in admin services - requires ADMIN role
     * Using bean pattern which handles inheritance properly
     */
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    static Advisor protectAdminServices() {
        AspectJExpressionPointcut pattern = new AspectJExpressionPointcut();
        pattern.setExpression(
                "bean(destinationService) || " +
                        "bean(sourceService) || " +
                        "bean(sourceAuthConfigService) || " +
                        "bean(userService) || " +
                        "bean(roleService)"
        );
        return new AuthorizationManagerBeforeMethodInterceptor(pattern,
                AuthorityAuthorizationManager.hasRole("ADMIN"));
    }

    /**
     * Protects ALL methods (including inherited) in user services - requires USER role or higher
     */
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    static Advisor protectUserServices() {
        AspectJExpressionPointcut pattern = new AspectJExpressionPointcut();
        pattern.setExpression("bean(prefectCredentialService)");
        return new AuthorizationManagerBeforeMethodInterceptor(pattern,
                AuthorityAuthorizationManager.hasAnyRole("USER", "ADMIN", "INTERNAL"));
    }

    @Bean
    public SecurityFilterChain vaadinSecurityFilterChain(HttpSecurity http) throws Exception {
        // Configure authorization rules
        http.authorizeHttpRequests(registry -> registry
                .requestMatchers(routeUtil::isRouteAllowed).permitAll()
                .requestMatchers("/login").permitAll()
                .requestMatchers("/login/oauth2/authorization/microsoft").permitAll()
                .requestMatchers("/login/oauth2/authorization/google").permitAll()
                .requestMatchers("/access-denied").permitAll()
                .requestMatchers("/error").permitAll()
        );

        // Configure OAuth2 login with custom user authorities mapper
        http.oauth2Login(oauth2 -> oauth2
                .loginPage("/login")
                .successHandler(oAuth2LoginSuccessHandler)
                .failureHandler(customAuthenticationFailureHandler)
                .userInfoEndpoint(userInfo -> userInfo
                        .userAuthoritiesMapper(customUserAuthoritiesMapper)
                )
        );

        // Configure session management for stateless
        http.sessionManagement(sessionManagement ->
                sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        );

        // Apply Vaadin-specific security configuration
        http.with(vaadin(), vaadin ->
                vaadin.loginView("/login", "/")
        );

        // Configure stateless authentication with JWT
        http.with(new VaadinStatelessSecurityConfigurer<>(),
                stateless -> stateless
                        .issuer("ai.goacquire.frontend")
                        .expiresIn(43200) // 12 hours in seconds
                        .withSecretKey()
                        .secretKey(
                                new SecretKeySpec(
                                        Base64.getDecoder().decode(authSecret),
                                        JwsAlgorithms.HS256
                                )
                        )
        );

        return http.build();
    }
}
1 Like