如何使用 vaadin 14 正确配置 spring-security 以处理 2 个入口点 - keycloak 和 DB

How to properly configure spring-security with vaadin14 to handle 2 entry points - keyclaok and DB

我有一个 vaadin14 应用程序,我想在不同的 url 路径上启用不同类型的身份验证机制。一个是测试url,其中身份验证应该使用DB,另一个是使用keycloak 的生产url。

我能够让每个身份验证机制单独工作,但是一旦我尝试同时使用这两个机制,我会得到意想不到的结果。

在这两种情况下,我都获得了登录页面,但身份验证无法正常工作。这是我的安全配置,我做错了什么?

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfiguration {


  @Configuration
  @Order(2)
  public static class DBAuthConfigurationAdapter extends WebSecurityConfigurerAdapter {


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

    /**
     * Require login to access internal pages and configure login form.
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

      // Not using Spring CSRF here to be able to use plain HTML for the login page
      http.csrf().disable()

        // Register our CustomRequestCache, that saves unauthorized access attempts, so
        // the user is redirected after login.
        .requestCache().requestCache(new CustomRequestCache())

        // Restrict access to our application.
        .and().antMatcher("/test**").authorizeRequests()

        // Allow all flow internal requests.
        .requestMatchers(SecurityUtils::isFrameworkInternalRequest).permitAll()

        // Allow all requests by logged in users.
        .anyRequest().hasRole("USER")

        // Configure the login page.
        .and().formLogin().loginPage(LOGIN_URL).permitAll().loginProcessingUrl(LOGIN_PROCESSING_URL)
        .failureUrl(LOGIN_FAILURE_URL)

        // Configure logout
        .and().logout().logoutSuccessUrl(LOGOUT_SUCCESS_URL);
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {

      Properties users = null;
      try {
        users = PropertiesLoaderUtils.loadAllProperties("users.properties");
        return new InMemoryUserDetailsManager(users);
      } catch (IOException e) {
        e.printStackTrace();
      }

      UserDetails user =
        User.withUsername("user")
        .password("{noop}password")
        .roles("ACTOR")
        .build();

      return new InMemoryUserDetailsManager(user);
    }

    /**
     * Allows access to static resources, bypassing Spring security.
     */
    @Override
    public void configure(WebSecurity web) {
      web.ignoring().antMatchers(
        // Vaadin Flow static resources
        "/VAADIN/**",
        // the standard favicon URI
        "/favicon.ico",
        // the robots exclusion standard
        "/robots.txt",
        // web application manifest
        "/manifest.webmanifest",
        "/sw.js",
        "/offline-page.html",
        // icons and images
        "/icons/**",
        "/images/**",
        // (development mode) static resources
        "/frontend/**",
        // (development mode) webjars
        "/webjars/**",
        // (development mode) H2 debugging console
        "/h2-console/**",
        // (production mode) static resources
        "/frontend-es5/**", "/frontend-es6/**",
        "/resources/**");
    }
  }

  @Order(1)
  @Configuration
  @ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
  public static class AppKeycloakSecurity extends KeycloakWebSecurityConfigurerAdapter {


    @Autowired
    public void configureGlobal(
      AuthenticationManagerBuilder auth) throws Exception {

      KeycloakAuthenticationProvider keycloakAuthenticationProvider
        = keycloakAuthenticationProvider();
      keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(
        new SimpleAuthorityMapper());
      auth.authenticationProvider(keycloakAuthenticationProvider);
    }

    @Bean
    public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
      return new KeycloakSpringBootConfigResolver();
    }


    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
      return new RegisterSessionAuthenticationStrategy(
        new SessionRegistryImpl());
    }

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

      http.httpBasic().disable();
      http.formLogin().disable();
      http.anonymous().disable();
      http.csrf().disable();
      http.headers().frameOptions().disable();

      http
        .antMatcher("/prod**")
        .authorizeRequests()
        .antMatchers("/vaadinServlet/UIDL/**").permitAll()
        .antMatchers("/vaadinServlet/HEARTBEAT/**").permitAll()
        .requestMatchers(SecurityUtils::isFrameworkInternalRequest).permitAll()
        .anyRequest().hasRole("actor");

      http
        .logout()
        .addLogoutHandler(keycloakLogoutHandler())
        .logoutUrl("/logout").permitAll()
        .logoutSuccessUrl("/");
      http
        .addFilterBefore(keycloakPreAuthActionsFilter(), LogoutFilter.class);
      http
        .exceptionHandling()
        .authenticationEntryPoint(authenticationEntryPoint());
      http
        .sessionManagement()
        .sessionAuthenticationStrategy(sessionAuthenticationStrategy());
    }
  }

}

在 Vaadin UI 中导航会更改浏览器中的 URL,但它不一定会创建准确 URL 的浏览器请求,有效绕过定义的访问控制Spring URL 的安全性。因此,Vaadin 确实不适合 Spring 提供的基于请求 URL 的安全方法。仅针对此问题,您可以查看我专门创建的附加组件 Spring Boot Security for Vaadin,以缩小 Spring 安全性和 Vaadin 之间的差距。

但是,虽然基于 URL 创建两个不同的 Spring 安全上下文相当容易,但出于同样的原因,这在 Vaadin 中将无法正常工作或根本无法工作。这甚至连我的插件都帮不上忙。

更新: 由于您可以选择结合两种安全上下文,因此我可以提供以下解决方案(使用我的附加组件): 从 Keycloak example 开始,您必须执行以下操作:

  1. 更改 WebSecurityConfig 以同时添加基于数据库的 AuthenticationProvider。添加您的 UserDetailsService 应该就足够了。确保给每个用户一个合适的角色。
  2. 您必须从 application.properties 中删除此行:codecamp.vaadin.security.standard-auth.enabled = false 这将通过 Vaadin 视图重新启用没有 Keycloak 的标准登录。
  3. 调整 KeycloakRouteAccessDeniedHandler 以忽略所有不应受 Keycloak 保护的测试视图。

我已经 prepared all this in Gitlab repo 并删除了对这个解决方案的要点不重要的所有内容。查看各个提交及其差异,也有助于关注重要部分。