[#1383] Spring Boot 3 Migration / Step 1 (#1384)

1. PagingAndSortingRepository doesn't extend CrudRepository anymore. For all extending that interface repositories CrudRepository super interface shall be now declared (https://spring.io/blog/2022/02/22/announcing-listcrudrepository-friends-for-spring-data-3-0 -
```
The popular PagingAndSortingRepository used to extend from CrudRepository, but it no longer does. This lets you combine it
with either CrudRepository or ListCrudRepository or a base interface of your own creation. This means you now have to
explicitly extend from a CRUD fragment, even when you already extend from PagingAndSortingRepository.
```
)
2. org.eclipse.hawkbit.autoconfigure.mgmt.ui -> move in hawkbit-ui (to be ready for removal), anyway - it's a better location for ui related configs
3. extends WebMvcConfigurerAdapter -> implements WebMvcConfigurer
4. remove WebSecurityConfigurerAdapter -> https://docs.spring.io/spring-security/reference/5.8/migration/servlet/config.html#_stop_using_websecurityconfigureradapter, https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter
and add @Order to the bean reg!!
5. Use configurers (the other will be deprecated / removed), e.d:  http.csrf().disable() -> http.csrf(AbstractHttpConfigurer::disable)
6. configure(final AuthenticationManagerBuilder auth) -> put in httpsecurity config - http.getSharedObject(AuthenticationManagerBuilder.class).... (https://www.baeldung.com/spring-security-authentication-provider)
7. configure(final WebSecurity webSecurity) ->
```
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
    return (web) -> web.ignoring().antMatchers("/documentation/**", "/VAADIN/**", "/*.*", "/docs/**");
}
```
(https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter)
8. AuthenticationManager authenticationManagerBean() ->
```
@Bean
AuthenticationManager authenticationManager(final AuthenticationConfiguration authenticationConfiguration) throws Exception {
    return authenticationConfiguration.getAuthenticationManager();
}
```
(https://backendstory.com/spring-security-how-to-replace-websecurityconfigureradapter/)
9. WebMvcAutoConfiguration could be removed - it uses deprectated methods, and sets properties that are same by default - hence - not neeeded
(https://github.com/spring-projects/spring-framework/issues/23915#issuecomment-563987147)

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2023-07-17 10:36:26 +03:00
committed by GitHub
parent bcc2616e73
commit 56ea5b15c9
27 changed files with 582 additions and 583 deletions

View File

@@ -0,0 +1,126 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.ui.autoconfigure;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.hawkbit.DistributedResourceBundleMessageSource;
import org.eclipse.hawkbit.ui.MgmtUiConfiguration;
import org.eclipse.hawkbit.ui.SpPermissionChecker;
import org.eclipse.hawkbit.ui.UiProperties;
import org.eclipse.hawkbit.ui.push.DelayedEventBusPushStrategy;
import org.eclipse.hawkbit.ui.push.EventPushStrategy;
import org.eclipse.hawkbit.ui.push.HawkbitEventPermissionChecker;
import org.eclipse.hawkbit.ui.push.HawkbitEventProvider;
import org.eclipse.hawkbit.ui.push.UIEventPermissionChecker;
import org.eclipse.hawkbit.ui.push.UIEventProvider;
import org.eclipse.hawkbit.ui.utils.SpringContextHolder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.vaadin.spring.annotation.EnableVaadinExtensions;
import org.vaadin.spring.events.EventBus.UIEventBus;
import org.vaadin.spring.events.annotation.EnableEventBus;
import com.vaadin.spring.annotation.UIScope;
/**
* The Management UI auto configuration.
*/
@Configuration
@EnableVaadinExtensions
@EnableEventBus
@ConditionalOnClass(MgmtUiConfiguration.class)
@Import(MgmtUiConfiguration.class)
public class MgmtUiAutoConfiguration {
@Bean
@ConditionalOnMissingBean
RedirectController uiRedirectController() {
return new RedirectController();
}
/**
* A message source bean to add distributed message sources.
*
* @return the message bean.
*/
@Bean(name = "messageSource")
DistributedResourceBundleMessageSource messageSource() {
return new DistributedResourceBundleMessageSource();
}
/**
* A event provider bean which hold the supported events for the UI.
*
* @return the provider bean
*/
@Bean
@ConditionalOnMissingBean
UIEventProvider eventProvider() {
return new HawkbitEventProvider();
}
/**
* A event permission checker bean which verifies supported events for the
* UI.
*
* @return the permission checker bean
*/
@Bean
@ConditionalOnMissingBean
UIEventPermissionChecker eventPermissionChecker(final SpPermissionChecker permChecker) {
return new HawkbitEventPermissionChecker(permChecker);
}
/**
* Bean for creating a singleton instance of the {@link SpringContextHolder}
*
* @return the singleton instance of the {@link SpringContextHolder}
*/
@Bean
SpringContextHolder springContextHolder() {
return SpringContextHolder.getInstance();
}
/**
* The UI scoped event push strategy. Session scope is necessary, that every
* UI has an own strategy.
*
* @param applicationContext
* the context to add the listener to
* @param executorService
* the general scheduler service
* @param eventBus
* the ui event bus
* @param eventProvider
* the event provider
* @param eventPermissionChecker
* the event permission checker
* @param uiProperties
* the ui properties
* @return the push strategy bean
*/
@Bean
@ConditionalOnMissingBean
@UIScope
EventPushStrategy eventPushStrategy(final ConfigurableApplicationContext applicationContext,
final ScheduledExecutorService executorService, final UIEventBus eventBus,
final UIEventProvider eventProvider, final UIEventPermissionChecker eventPermissionChecker,
final UiProperties uiProperties) {
final DelayedEventBusPushStrategy delayedEventBusPushStrategy = new DelayedEventBusPushStrategy(executorService,
eventBus, eventProvider, eventPermissionChecker, uiProperties.getEvent().getPush().getDelay());
applicationContext.addApplicationListener(delayedEventBusPushStrategy);
return delayedEventBusPushStrategy;
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.ui.autoconfigure;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
/**
* Redirects for convenience. hawkBit's management UI is by default not
* listening on / but on /UI.
*
*/
@Controller
public class RedirectController {
/**
* @return redirect to the Management UI
*/
@GetMapping("/")
public ModelAndView home() {
return new ModelAndView("redirect:/UI/");
}
}

View File

@@ -0,0 +1,306 @@
/**
* Copyright (c) 2021 Bosch.IO GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.ui.autoconfigure;
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.security.DosFilter;
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.ui.MgmtUiConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.firewall.FirewalledRequest;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.StrictHttpFirewall;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.vaadin.spring.http.HttpService;
import org.vaadin.spring.security.annotation.EnableVaadinSharedSecurity;
import org.vaadin.spring.security.config.VaadinSharedSecurityConfiguration;
import org.vaadin.spring.security.shared.VaadinAuthenticationSuccessHandler;
import org.vaadin.spring.security.shared.VaadinUrlAuthenticationSuccessHandler;
import org.vaadin.spring.security.web.VaadinRedirectStrategy;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* {@link WebSecurityConfigurer} for external (management) access.
*/
@Configuration
@EnableWebSecurity
@EnableVaadinSharedSecurity
@ConditionalOnClass(MgmtUiConfiguration.class)
public class UISecurityConfigurationAdapter {
private static final Logger LOG = LoggerFactory.getLogger(UISecurityConfigurationAdapter.class);
private static final int DOS_FILTER_ORDER = -200;
@Autowired
private HawkbitSecurityProperties hawkbitSecurityProperties;
/**
* Filter to protect the hawkBit management UI against to many requests.
*
* @param securityProperties for filter configuration
* @return the spring filter registration bean for registering a denial
* of service protection filter in the filter chain
*/
@Bean
@ConditionalOnProperty(prefix = "hawkbit.server.security.dos.ui-filter", name = "enabled", matchIfMissing = true)
public FilterRegistrationBean<DosFilter> dosMgmtUiFilter(final HawkbitSecurityProperties securityProperties) {
final HawkbitSecurityProperties.Dos.Filter filterProperties = securityProperties.getDos().getUiFilter();
final HawkbitSecurityProperties.Clients clientProperties = securityProperties.getClients();
final FilterRegistrationBean<DosFilter> filterRegBean = new FilterRegistrationBean<>();
filterRegBean.setFilter(new DosFilter(null, filterProperties.getMaxRead(),
filterProperties.getMaxWrite(), filterProperties.getWhitelist(), clientProperties.getBlacklist(),
clientProperties.getRemoteIpHeader()));
// All URLs that can be called anonymous
filterRegBean.setUrlPatterns(Arrays.asList("/UI/login", "/UI/login/*", "/UI/logout", "/UI/logout/*"));
filterRegBean.setOrder(DOS_FILTER_ORDER);
filterRegBean.setName("dosMgmtUiFilter");
return filterRegBean;
}
@Bean
AuthenticationManager authenticationManager(final AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
/**
* Overwriting VaadinAuthenticationSuccessHandler of default
* VaadinSharedSecurityConfiguration
*
* @return the vaadin success authentication handler
*/
@Primary
@Bean(name = VaadinSharedSecurityConfiguration.VAADIN_AUTHENTICATION_SUCCESS_HANDLER_BEAN)
public VaadinAuthenticationSuccessHandler redirectSaveHandler(final HttpService httpService,
final VaadinRedirectStrategy redirectStrategy) {
final VaadinUrlAuthenticationSuccessHandler handler = new TenantMetadataSavedRequestAwareVaadinAuthenticationSuccessHandler(
httpService, redirectStrategy, "/UI/");
handler.setTargetUrlParameter("r");
return handler;
}
/**
* Listener to redirect to login page after session timeout. Close the
* vaadin session, because it's is not possible to redirect in
* atmosphere.
*
* @return the servlet listener.
*/
@Bean
public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
return new ServletListenerRegistrationBean<>(new HttpSessionEventPublisher());
}
@Bean
@Order(400)
protected SecurityFilterChain filterChainUI(
final HttpSecurity http,
@Autowired(required = false)
final OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService,
@Autowired(required = false)
final AuthenticationSuccessHandler authenticationSuccessHandler,
final LogoutHandler logoutHandler,
final LogoutSuccessHandler logoutSuccessHandler)
throws Exception {
final boolean enableOidc = oidcUserService != null && authenticationSuccessHandler != null;
// workaround regex: we need to exclude the URL /UI/HEARTBEAT here
// because we bound the vaadin application to /UI and not to root,
// described in vaadin-forum:
// https://vaadin.com/forum#!/thread/3200565.
HttpSecurity httpSec;
if (enableOidc) {
httpSec = http.requestMatchers().antMatchers("/**/UI/**", "/**/oauth2/**").and();
} else {
httpSec = http.antMatcher("/**/UI/**");
}
// disable as CSRF is handled by Vaadin
httpSec.csrf(AbstractHttpConfigurer::disable);
// allow same origin X-Frame-Options for correct file download under
// Safari
httpSec.headers().frameOptions().sameOrigin();
if (hawkbitSecurityProperties.isRequireSsl()) {
httpSec = httpSec.requiresChannel(crmRegistry -> crmRegistry.anyRequest().requiresSecure());
} else {
LOG.info(
"""
******************
** Requires HTTPS Security has been disabled for UI, should only be used for developing purposes **
******************""");
}
if (!ObjectUtils.isEmpty(hawkbitSecurityProperties.getContentSecurityPolicy())) {
httpSec.headers().contentSecurityPolicy(hawkbitSecurityProperties.getContentSecurityPolicy());
}
// UI
httpSec.authorizeRequests().antMatchers("/UI/login/**", "/UI/UIDL/**").permitAll().anyRequest()
.authenticated();
if (enableOidc) {
// OIDC
httpSec.oauth2Login().userInfoEndpoint().oidcUserService(oidcUserService).and()
.successHandler(authenticationSuccessHandler).and().oauth2Client();
} else {
// UI login / Basic auth
httpSec.exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/UI/login"));
}
// UI logout
httpSec.logout().logoutUrl("/UI/logout*").addLogoutHandler(logoutHandler)
.logoutSuccessHandler(logoutSuccessHandler);
return httpSec.build();
}
/**
* HttpFirewall which enables to define a list of allowed host names.
*
* @return the http firewall.
*/
@Bean
public HttpFirewall httpFirewall() {
final List<String> allowedHostNames = hawkbitSecurityProperties.getAllowedHostNames();
final IgnorePathsStrictHttpFirewall firewall = new IgnorePathsStrictHttpFirewall(
hawkbitSecurityProperties.getHttpFirewallIgnoredPaths());
if (!CollectionUtils.isEmpty(allowedHostNames)) {
firewall.setAllowedHostnames(hostName -> {
LOG.debug("Firewall check host: {}, allowed: {}", hostName, allowedHostNames.contains(hostName));
return allowedHostNames.contains(hostName);
});
}
return firewall;
}
private static class IgnorePathsStrictHttpFirewall extends StrictHttpFirewall {
private final Collection<String> pathsToIgnore;
public IgnorePathsStrictHttpFirewall(final Collection<String> pathsToIgnore) {
super();
this.pathsToIgnore = pathsToIgnore;
}
@Override
public FirewalledRequest getFirewalledRequest(final HttpServletRequest request) {
if (pathsToIgnore != null && pathsToIgnore.contains(request.getRequestURI())) {
return new FirewalledRequest(request) {
@Override
public void reset() {
// nothing to do
}
};
}
return super.getFirewalledRequest(request);
}
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
// No security for static content
return (web) -> web.ignoring().antMatchers("/documentation/**", "/VAADIN/**", "/*.*", "/docs/**");
}
/**
* Configuration that defines the {@link AccessDecisionManager} bean for
* UI method security used by the Vaadin Servlet. Notice: we can not use
* the top-level method security configuration because
* AdviceMode.ASPECTJ is not supported.
*/
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true)
@ConditionalOnClass(MgmtUiConfiguration.class)
static class UIMethodSecurity extends GlobalMethodSecurityConfiguration {
@Bean(name = VaadinSharedSecurityConfiguration.ACCESS_DECISION_MANAGER_BEAN)
@Override
protected AccessDecisionManager accessDecisionManager() {
return super.accessDecisionManager();
}
}
/**
* After a successful login on the UI we need to ensure to create the tenant
* meta data within SP.
*/
class TenantMetadataSavedRequestAwareVaadinAuthenticationSuccessHandler extends VaadinUrlAuthenticationSuccessHandler {
@Autowired
private SystemManagement systemManagement;
@Autowired
private SystemSecurityContext systemSecurityContext;
public TenantMetadataSavedRequestAwareVaadinAuthenticationSuccessHandler(final HttpService http,
final VaadinRedirectStrategy redirectStrategy, final String defaultTargetUrl) {
super(http, redirectStrategy, defaultTargetUrl);
}
@Override
public void onAuthenticationSuccess(final Authentication authentication) throws Exception {
systemSecurityContext.runAsSystemAsTenant(systemManagement::getTenantMetadata, getTenantFrom(authentication));
super.onAuthenticationSuccess(authentication);
}
private static String getTenantFrom(final Authentication authentication) {
final Object details = authentication.getDetails();
if (details instanceof TenantAwareAuthenticationDetails) {
return ((TenantAwareAuthenticationDetails) details).getTenant();
}
throw new InsufficientAuthenticationException("Authentication details/tenant info are not specified!");
}
}
}

View File

@@ -0,0 +1,3 @@
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.eclipse.hawkbit.ui.autoconfigure.MgmtUiAutoConfiguration