如何使用 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 开始,您必须执行以下操作:
- 更改
WebSecurityConfig
以同时添加基于数据库的 AuthenticationProvider
。添加您的 UserDetailsService
应该就足够了。确保给每个用户一个合适的角色。
- 您必须从
application.properties
中删除此行:codecamp.vaadin.security.standard-auth.enabled = false
这将通过 Vaadin 视图重新启用没有 Keycloak 的标准登录。
- 调整
KeycloakRouteAccessDeniedHandler
以忽略所有不应受 Keycloak 保护的测试视图。
我已经 prepared all this in Gitlab repo 并删除了对这个解决方案的要点不重要的所有内容。查看各个提交及其差异,也有助于关注重要部分。
我有一个 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 开始,您必须执行以下操作:
- 更改
WebSecurityConfig
以同时添加基于数据库的AuthenticationProvider
。添加您的UserDetailsService
应该就足够了。确保给每个用户一个合适的角色。 - 您必须从
application.properties
中删除此行:codecamp.vaadin.security.standard-auth.enabled = false
这将通过 Vaadin 视图重新启用没有 Keycloak 的标准登录。 - 调整
KeycloakRouteAccessDeniedHandler
以忽略所有不应受 Keycloak 保护的测试视图。
我已经 prepared all this in Gitlab repo 并删除了对这个解决方案的要点不重要的所有内容。查看各个提交及其差异,也有助于关注重要部分。