Reputation: 17
I have a vaadin14 application that I want to enable different types of authentication mechanisms on different url paths. One is a test url, where authentication should use DB, and the other is the production url that uses keycloak.
I was able to get each authentication mechanism to work separately, but once I try to put both, I get unexpected results.
In both cases, I get login page, but the authentication doesn't work correctly. Here's my security configuration, what am I doing wrong?
@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());
}
}
}
Upvotes: 1
Views: 590
Reputation: 2271
Navigating within a Vaadin UI will change the URL in your browser, but it will not necessarily create a browser request to that exact URL, effectively bypassing the access control defined by Spring security for that URL. As such, Vaadin is really not suited for the request URL-based security approach that Spring provides. For this issue alone you could take a look at my add-on Spring Boot Security for Vaadin which I specifically created to close the gap between Spring security and Vaadin.
But while creating two distinct Spring security contexts based on the URL is fairly easy, this - for the same reason - will not work well or at all with Vaadin. And that's something even my add-on couldn't help with.
Update: As combining both security contexts is an option for you, I can offer the following solution (using my add-on): Starting from the Keycloak example, you would have to do the following:
WebSecurityConfig
to also add your DB-based AuthenticationProvider
. Adding your UserDetailsService
should still be enough. Make sure to give every user a suitable role.application.properties
: codecamp.vaadin.security.standard-auth.enabled = false
This will re-enable the standard login without Keycloak via a Vaadin view.KeycloakRouteAccessDeniedHandler
to ignore all test views that shouldn't be protected by Keycloak.I already prepared all this in Gitlab repo and removed everything not important for the main point of this solution. See the individual commits and their diffs to also help focus in on the important bits.
Upvotes: 3