From 3a34ded4f6de9e5c2d526ab50211fbc3b1f0eba1 Mon Sep 17 00:00:00 2001 From: Avgustin Marinov Date: Fri, 26 Jul 2024 10:59:15 +0300 Subject: [PATCH] Support for simultaneous base and OAuth authentication (#1785) * Remove _OidcAuthenticationSuccessHandler_: * _OAuth2AuthenticationToken.setDetails_ is made by jwt authentication converter * get tenant data (with potentially creating tenant) is done via a filter added in filterChainREST * _filterChainREST_ uses _Customizer>_ as configuration for OAuth. Thus it is not bound with oauth client configuration * _OidcUserManagementAutoConfiguration_ - now registers (if conditions are met) Customizer> which covers both - oauth legacy filter from filterChainREST and OidcBearerTokenAuthenticationFilter * Since oauth clients are not related to hawkBit anymore (since removal of legacy UI) and the proper configuration would be via resource server or whatever, the _OidcUserManagementAutoConfiguration_ is DEPRECATED and for removal * _UserAuthenticationFilter_ is removed * Enabled sumiltaneous base and oauth authentication. Still, by default, if OAuth configured http authentication is disabled. However, if OAuth it is configured (via _Customizer>)_ and **hawkbit.server.security.allowHttpBasicOnOAuthEnabled** is set to **true** then http auth would be also enabled * _OidcUserManagementAutoConfiguration_ could be disabled with **hawkbit.server.security.oAuth2OnClientsConfig.enabled=false** Signed-off-by: Marinov Avgustin --- .../OidcUserManagementAutoConfiguration.java | 181 +++++----------- .../SecurityManagedConfiguration.java | 196 ++++-------------- .../hawkbit-security-defaults.properties | 4 +- .../UserAuthenticationFilter.java | 66 ------ .../security/HawkbitSecurityProperties.java | 4 + 5 files changed, 100 insertions(+), 351 deletions(-) delete mode 100644 hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/UserAuthenticationFilter.java diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/OidcUserManagementAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/OidcUserManagementAutoConfiguration.java index f8e53bf61..498fd2e90 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/OidcUserManagementAutoConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/OidcUserManagementAutoConfiguration.java @@ -9,42 +9,32 @@ */ package org.eclipse.hawkbit.autoconfigure.security; -import java.io.IOException; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; -import jakarta.servlet.Filter; -import jakarta.servlet.FilterChain; -import jakarta.servlet.FilterConfig; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails; -import org.eclipse.hawkbit.im.authentication.UserAuthenticationFilter; -import org.eclipse.hawkbit.repository.SystemManagement; -import org.eclipse.hawkbit.security.SystemSecurityContext; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.security.oauth2.client.ClientsConfiguredCondition; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.core.Authentication; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService; import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; @@ -56,31 +46,24 @@ import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.jwt.JwtDecoders; import org.springframework.security.oauth2.jwt.JwtException; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; +import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; /** - * Auto-configuration for OpenID Connect user management. + * Auto-configuration for OpenID Connect user management. Based on clients configuration. * + * @deprecated hawkBit doesn't use/depend on clients configuration (it was back in time of integrated UI. */ @Configuration @Conditional(value = ClientsConfiguredCondition.class) +@ConditionalOnProperty(prefix = "hawkbit.server.security.oAuth2OnClientsConfig", name = "enabled", havingValue = "true", matchIfMissing = true) +@Deprecated(forRemoval = true) public class OidcUserManagementAutoConfiguration { - /** - * @return the OpenID Connect authentication success handler - */ - @Bean - public AuthenticationSuccessHandler oidcAuthenticationSuccessHandler( - final SystemManagement systemManagement, final SystemSecurityContext systemSecurityContext) { - return new OidcAuthenticationSuccessHandler(systemManagement, systemSecurityContext); - } - /** * @return a jwt authorities extractor which interprets the roles of a user * as their authorities. @@ -105,16 +88,43 @@ public class OidcUserManagementAutoConfiguration { return new JwtAuthoritiesOidcUserService(extractor); } - /** - * @return an authentication filter for using OAuth2 Bearer Tokens. - */ @Bean @ConditionalOnMissingBean - OidcBearerTokenAuthenticationFilter oidcBearerTokenAuthenticationFilter( - final JwtAuthoritiesExtractor authoritiesExtractor, - final SystemManagement systemManagement, final SystemSecurityContext systemSecurityContext) { - return new OidcBearerTokenAuthenticationFilter( - authoritiesExtractor, systemManagement, systemSecurityContext); + Customizer> oauth2ResourceServerCustomizer( + final InMemoryClientRegistrationRepository clientRegistrationRepository, + final JwtAuthoritiesExtractor authoritiesExtractor) { + // Only get the first client registration. Testing against every client could increase the attack vector (?) + final ClientRegistration clientRegistration = + clientRegistrationRepository.iterator().hasNext() ? clientRegistrationRepository.iterator().next() : null; + Assert.notNull(clientRegistration, "There must be a valid client registration"); + + return configurer -> configurer.jwt(configurer2 -> { + if (clientRegistration.getProviderDetails().getJwkSetUri() == null) { + configurer2.decoder(JwtDecoders.fromIssuerLocation(clientRegistration.getProviderDetails().getIssuerUri())); + } else { + configurer2.jwkSetUri(clientRegistration.getProviderDetails().getJwkSetUri()); + } + configurer2.jwtAuthenticationConverter(jwt -> { + final String defaultTenant = "DEFAULT"; + + final OidcIdToken idToken = new OidcIdToken( + jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims()); + final OidcUserInfo userInfo = new OidcUserInfo(jwt.getClaims()); + + final Set authorities = authoritiesExtractor.extract(jwt, clientRegistration); + if (authorities.isEmpty()) { + throw new AccessDeniedException("No authorities found in token"); + } + + final DefaultOidcUser user = new DefaultOidcUser(authorities, idToken, userInfo); + + final OAuth2AuthenticationToken oAuth2AuthenticationToken = new OAuth2AuthenticationToken( + user, authorities, clientRegistration.getRegistrationId()); + + oAuth2AuthenticationToken.setDetails(new TenantAwareAuthenticationDetails(defaultTenant, false)); + return oAuth2AuthenticationToken; + }); + }); } /** @@ -166,42 +176,11 @@ public class OidcUserManagementAutoConfiguration { } } - /** - * OpenID Connect Authentication Success Handler which load tenant data - */ - private static class OidcAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { - - private final SystemManagement systemManagement; - private final SystemSecurityContext systemSecurityContext; - - OidcAuthenticationSuccessHandler( - final SystemManagement systemManagement, final SystemSecurityContext systemSecurityContext) { - this.systemManagement = systemManagement; - this.systemSecurityContext = systemSecurityContext; - } - - @Override - public void onAuthenticationSuccess( - final HttpServletRequest request, final HttpServletResponse response, - final Authentication authentication) throws ServletException, IOException { - if (authentication instanceof AbstractAuthenticationToken token) { - final String defaultTenant = "DEFAULT"; - - token.setDetails(new TenantAwareAuthenticationDetails(defaultTenant, false)); - - systemSecurityContext.runAsSystemAsTenant(systemManagement::getTenantMetadata, defaultTenant); - } - - super.onAuthenticationSuccess(request, response, authentication); - } - } - /** * Utility class to extract authorities out of the jwt. It interprets the user's * role as their authorities. */ - private record DefaultJwtAuthoritiesExtractor - (GrantedAuthoritiesMapper authoritiesMapper) implements JwtAuthoritiesExtractor { + private record DefaultJwtAuthoritiesExtractor(GrantedAuthoritiesMapper authoritiesMapper) implements JwtAuthoritiesExtractor { private static final OAuth2Error INVALID_REQUEST = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST); @@ -240,68 +219,4 @@ public class OidcUserManagementAutoConfiguration { return new LinkedHashSet<>(authorities); } } - - static class OidcBearerTokenAuthenticationFilter implements UserAuthenticationFilter, Filter { - - private final JwtAuthoritiesExtractor authoritiesExtractor; - private final SystemManagement systemManagement; - private final SystemSecurityContext systemSecurityContext; - - private ClientRegistration clientRegistration; - - OidcBearerTokenAuthenticationFilter( - final JwtAuthoritiesExtractor authoritiesExtractor, - final SystemManagement systemManagement, final SystemSecurityContext systemSecurityContext) { - this.authoritiesExtractor = authoritiesExtractor; - this.systemManagement = systemManagement; - this.systemSecurityContext = systemSecurityContext; - } - - void setClientRegistration(final ClientRegistration clientRegistration) { - this.clientRegistration = clientRegistration; - } - - @Override - public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) - throws IOException, ServletException { - final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication instanceof JwtAuthenticationToken jwtAuthenticationToken) { - final String defaultTenant = "DEFAULT"; - - final Jwt jwt = jwtAuthenticationToken.getToken(); - final OidcIdToken idToken = new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), - jwt.getClaims()); - final OidcUserInfo userInfo = new OidcUserInfo(jwt.getClaims()); - - final Set authorities = authoritiesExtractor.extract(jwt, clientRegistration); - - if (authorities.isEmpty()) { - ((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN); - return; - } - - final DefaultOidcUser user = new DefaultOidcUser(authorities, idToken, userInfo); - - final OAuth2AuthenticationToken oAuth2AuthenticationToken = new OAuth2AuthenticationToken(user, authorities, - clientRegistration.getRegistrationId()); - - oAuth2AuthenticationToken.setDetails(new TenantAwareAuthenticationDetails(defaultTenant, false)); - - systemSecurityContext.runAsSystemAsTenant(systemManagement::getTenantMetadata, defaultTenant); - SecurityContextHolder.getContext().setAuthentication(oAuth2AuthenticationToken); - } - - chain.doFilter(request, response); - } - - @Override - public void init(final FilterConfig filterConfig) { - // Nothing to do - } - - @Override - public void destroy() { - // Nothing to do - } - } } diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java index 14d6e2be7..5819c1323 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java @@ -9,17 +9,10 @@ */ package org.eclipse.hawkbit.autoconfigure.security; -import java.io.IOException; import java.util.Collection; import java.util.Collections; import java.util.List; -import jakarta.servlet.Filter; -import jakarta.servlet.FilterChain; -import jakarta.servlet.FilterConfig; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; @@ -28,7 +21,6 @@ import org.eclipse.hawkbit.ddi.rest.api.DdiRestConstants; import org.eclipse.hawkbit.ddi.rest.resource.DdiApiConfiguration; import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; -import org.eclipse.hawkbit.im.authentication.UserAuthenticationFilter; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.mgmt.rest.resource.MgmtApiConfiguration; import org.eclipse.hawkbit.repository.ControllerManagement; @@ -48,13 +40,11 @@ import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.tenancy.TenantAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.AdviceMode; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.PropertySource; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; @@ -62,31 +52,23 @@ import org.springframework.http.HttpStatus; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -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.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; -import org.springframework.security.oauth2.jwt.JwtDecoders; -import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter; import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; +import org.springframework.security.web.access.intercept.AuthorizationFilter; import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; -import org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter; import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; -import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; 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.SessionManagementFilter; -import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; @@ -104,31 +86,13 @@ import org.springframework.web.cors.CorsConfigurationSource; public class SecurityManagedConfiguration { private static final int DOS_FILTER_ORDER = -200; + public static final String ANONYMOUS_CONTROLLER_SECURITY_ENABLED_SHOULD_ONLY_BE_USED_FOR_DEVELOPMENT_PURPOSES = """ + ****************** + ** Anonymous controller security enabled, should only be used for development purposes ** + ******************"""; /** - * @return the {@link UserAuthenticationFilter} to include into the hawkBit security configuration. - * @throws Exception lazy bean exception maybe if the authentication manager cannot be instantiated - */ - @Bean - @ConditionalOnMissingBean - // Exception squid:S00112 - Is aspectJ proxy - @SuppressWarnings({ "squid:S00112" }) - UserAuthenticationFilter userAuthenticationFilter(final AuthenticationConfiguration configuration) - throws Exception { - return new UserAuthenticationFilterBasicAuth(configuration.getAuthenticationManager()); - } - - private static final class UserAuthenticationFilterBasicAuth extends BasicAuthenticationFilter - implements UserAuthenticationFilter { - - private UserAuthenticationFilterBasicAuth(final AuthenticationManager authenticationManager) { - super(authenticationManager); - } - - } - - /** - * {@link WebSecurityConfigurer} for the hawkBit server DDI interface. + * Security configuration for the hawkBit server DDI interface. */ @Configuration @EnableWebSecurity @@ -165,18 +129,14 @@ public class SecurityManagedConfiguration { } /** - * Filter to protect the hawkBit server DDI interface against to many - * requests. + * Filter to protect the hawkBit server DDI interface against too 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 + * @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.filter", name = "enabled", matchIfMissing = true) - public FilterRegistrationBean dosFilterDDI(final HawkbitSecurityProperties securityProperties) { + protected FilterRegistrationBean dosFilterDDI(final HawkbitSecurityProperties securityProperties) { final FilterRegistrationBean filterRegBean = dosFilter(List.of(DDI_ANT_MATCHERS), securityProperties.getDos().getFilter(), securityProperties.getClients()); @@ -201,11 +161,7 @@ public class SecurityManagedConfiguration { final ControllerTenantAwareAuthenticationDetailsSource authenticationDetailsSource = new ControllerTenantAwareAuthenticationDetailsSource(); if (ddiSecurityConfiguration.getAuthentication().getAnonymous().isEnabled()) { - log.info( - """ - ****************** - ** Anonymous controller security enabled, should only be used for developing purposes ** - ******************"""); + log.warn(ANONYMOUS_CONTROLLER_SECURITY_ENABLED_SHOULD_ONLY_BE_USED_FOR_DEVELOPMENT_PURPOSES); final AnonymousAuthenticationFilter anonymousFilter = new AnonymousAuthenticationFilter( "controllerAnonymousFilter", "anonymous", @@ -253,8 +209,7 @@ public class SecurityManagedConfiguration { } /** - * {@link WebSecurityConfigurer} for the hawkBit server DDI download - * interface. + * Security configuration for the hawkBit server DDI download interface. */ @Configuration @ConditionalOnClass(DdiApiConfiguration.class) @@ -284,14 +239,10 @@ public class SecurityManagedConfiguration { } /** - * Filter to protect the hawkBit server DDI download interface against - * to many requests. + * Filter to protect the hawkBit server DDI download interface against too 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 + * @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.filter", name = "enabled", matchIfMissing = true) @@ -320,11 +271,7 @@ public class SecurityManagedConfiguration { final ControllerTenantAwareAuthenticationDetailsSource authenticationDetailsSource = new ControllerTenantAwareAuthenticationDetailsSource(); if (ddiSecurityConfiguration.getAuthentication().getAnonymous().isEnabled()) { - log.info( - """ - ****************** - ** Anonymous controller security enabled, should only be used for developing purposes ** - ******************"""); + log.warn(ANONYMOUS_CONTROLLER_SECURITY_ENABLED_SHOULD_ONLY_BE_USED_FOR_DEVELOPMENT_PURPOSES); final AnonymousAuthenticationFilter anonymousFilter = new AnonymousAuthenticationFilter( "controllerAnonymousFilter", "anonymous", @@ -377,14 +324,10 @@ public class SecurityManagedConfiguration { } /** - * Filter to protect the hawkBit server system management interface against - * to many requests. + * Filter to protect the hawkBit server system management interface against too 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 + * @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.filter", name = "enabled", matchIfMissing = true) @@ -411,7 +354,7 @@ public class SecurityManagedConfiguration { } /** - * A Websecurity config to handle and filter the download ids. + * Security config to handle and filter the download ids. */ @Configuration @EnableWebSecurity @@ -435,7 +378,7 @@ public class SecurityManagedConfiguration { .authorizeHttpRequests(armrRepository -> armrRepository.anyRequest().authenticated()) .csrf(AbstractHttpConfigurer::disable) .anonymous(AbstractHttpConfigurer::disable) - .addFilterBefore(downloadIdAuthenticationFilter, FilterSecurityInterceptor.class) + .addFilterBefore(downloadIdAuthenticationFilter, AuthorizationFilter.class) .sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); return http.build(); @@ -460,15 +403,17 @@ public class SecurityManagedConfiguration { * Filter to protect the hawkBit server Management interface against to * many requests. * - * @return the spring filter registration bean for registering a denial - * of service protection filter in the filter chain + * @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.filter", name = "enabled", matchIfMissing = true) public FilterRegistrationBean dosFilterREST() { final FilterRegistrationBean filterRegBean = dosFilter(null, securityProperties.getDos().getFilter(), securityProperties.getClients()); - filterRegBean.setUrlPatterns(List.of("/rest/*", "/api/*")); + filterRegBean.setUrlPatterns(List.of( + MgmtRestConstants.BASE_REST_MAPPING + "/*", + MgmtRestConstants.BASE_SYSTEM_MAPPING + "/admin/*", + MgmtRestConstants.DOWNLOAD_ID_V1_REQUEST_MAPPING_BASE + "/*")); filterRegBean.setOrder(DOS_FILTER_ORDER); filterRegBean.setName("dosMgmtFilter"); @@ -479,25 +424,22 @@ public class SecurityManagedConfiguration { @Order(350) SecurityFilterChain filterChainREST( final HttpSecurity http, - @Lazy - final UserAuthenticationFilter userAuthenticationFilter, @Autowired(required = false) - final OidcUserManagementAutoConfiguration.OidcBearerTokenAuthenticationFilter - oidcBearerTokenAuthenticationFilter, - @Autowired(required = false) - final InMemoryClientRegistrationRepository clientRegistrationRepository, + final Customizer> oauth2ResourceServerCustomizer, final SystemManagement systemManagement, - final SystemSecurityContext systemSecurityContext) - throws Exception { + final SystemSecurityContext systemSecurityContext) throws Exception { http - .securityMatcher("/rest/**", MgmtRestConstants.BASE_SYSTEM_MAPPING + "/admin/**") - .csrf(AbstractHttpConfigurer::disable) + .securityMatcher(MgmtRestConstants.BASE_REST_MAPPING + "/**", MgmtRestConstants.BASE_SYSTEM_MAPPING + "/admin/**") .authorizeHttpRequests(amrmRegistry -> amrmRegistry .requestMatchers(MgmtRestConstants.BASE_SYSTEM_MAPPING + "/admin/**") .hasAnyAuthority(SpPermission.SYSTEM_ADMIN) .anyRequest() .authenticated()) + .anonymous(AbstractHttpConfigurer::disable) + .csrf(AbstractHttpConfigurer::disable) + .exceptionHandling(Customizer.withDefaults()) + .sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .addFilterAfter( // Servlet filter to create metadata after successful authentication over RESTful. (request, response, chain) -> { @@ -507,9 +449,7 @@ public class SecurityManagedConfiguration { } chain.doFilter(request, response); }, - SessionManagementFilter.class) - .anonymous(AbstractHttpConfigurer::disable) - .sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); + SessionManagementFilter.class); if (securityProperties.getCors().isEnabled()) { http.cors(configurer -> configurer.configurationSource(corsConfigurationSource())); @@ -519,64 +459,21 @@ public class SecurityManagedConfiguration { http.requiresChannel(crmRegistry -> crmRegistry.anyRequest().requiresSecure()); } - if (oidcBearerTokenAuthenticationFilter != null) { - // Only get the first client registration. Testing against every - // client could increase the - // attack vector - final ClientRegistration clientRegistration = clientRegistrationRepository != null - && clientRegistrationRepository.iterator().hasNext() - ? clientRegistrationRepository.iterator().next() - : null; - - Assert.notNull(clientRegistration, "There must be a valid client registration"); - http.oauth2ResourceServer(configurer -> configurer.jwt(configurer2 -> { - if (clientRegistration.getProviderDetails().getJwkSetUri() == null) { - configurer2.decoder(JwtDecoders.fromIssuerLocation(clientRegistration.getProviderDetails().getIssuerUri())); - } else { - configurer2.jwkSetUri(clientRegistration.getProviderDetails().getJwkSetUri()); - } - })); - - oidcBearerTokenAuthenticationFilter.setClientRegistration(clientRegistration); - - http.addFilterAfter(oidcBearerTokenAuthenticationFilter, BearerTokenAuthenticationFilter.class); - } else { - final BasicAuthenticationEntryPoint basicAuthEntryPoint = new BasicAuthenticationEntryPoint(); - basicAuthEntryPoint.setRealmName(securityProperties.getBasicRealm()); - - http.addFilterBefore(new Filter() { - @Override - public void init(final FilterConfig filterConfig) throws ServletException { - userAuthenticationFilter.init(filterConfig); - } - - @Override - public void doFilter(final ServletRequest request, final ServletResponse response, - final FilterChain chain) throws IOException, ServletException { - userAuthenticationFilter.doFilter(request, response, chain); - } - - @Override - public void destroy() { - userAuthenticationFilter.destroy(); - } - }, RequestHeaderAuthenticationFilter.class); - http - .httpBasic(Customizer.withDefaults()) - .exceptionHandling(configurer -> configurer.authenticationEntryPoint(basicAuthEntryPoint)); + if (oauth2ResourceServerCustomizer != null) { + http.oauth2ResourceServer(oauth2ResourceServerCustomizer); + } + if (oauth2ResourceServerCustomizer == null || securityProperties.isAllowHttpBasicOnOAuthEnabled()) { + http.httpBasic(configurer -> { + final BasicAuthenticationEntryPoint basicAuthEntryPoint = new BasicAuthenticationEntryPoint(); + basicAuthEntryPoint.setRealmName(securityProperties.getBasicRealm()); + configurer.authenticationEntryPoint(basicAuthEntryPoint); + }); } return http.build(); } - @Bean - @ConditionalOnProperty(prefix = "hawkbit.server.security.cors", name = "enabled") - CorsConfigurationSource corsConfigurationSource() { - final CorsConfiguration configuration = corsConfiguration(); - return request -> configuration; - } - - private CorsConfiguration corsConfiguration() { + private CorsConfigurationSource corsConfigurationSource() { final CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.setAllowedOrigins(securityProperties.getCors().getAllowedOrigins()); @@ -584,8 +481,7 @@ public class SecurityManagedConfiguration { corsConfiguration.setAllowedHeaders(securityProperties.getCors().getAllowedHeaders()); corsConfiguration.setAllowedMethods(securityProperties.getCors().getAllowedMethods()); corsConfiguration.setExposedHeaders(securityProperties.getCors().getExposedHeaders()); - - return corsConfiguration; + return request -> corsConfiguration; } } diff --git a/hawkbit-autoconfigure/src/main/resources/hawkbit-security-defaults.properties b/hawkbit-autoconfigure/src/main/resources/hawkbit-security-defaults.properties index 369481b21..de92ed78d 100644 --- a/hawkbit-autoconfigure/src/main/resources/hawkbit-security-defaults.properties +++ b/hawkbit-autoconfigure/src/main/resources/hawkbit-security-defaults.properties @@ -13,5 +13,5 @@ hawkbit.server.ddi.security.authentication.header.enabled=false hawkbit.server.ddi.security.authentication.header.authority= hawkbit.server.ddi.security.authentication.targettoken.enabled=false hawkbit.server.ddi.security.authentication.gatewaytoken.enabled=false -hawkbit.server.download.anonymous.enabled=false -hawkbit.server.ddi.security.authentication.gatewaytoken.key= \ No newline at end of file +hawkbit.server.ddi.security.authentication.gatewaytoken.key= +hawkbit.server.download.anonymous.enabled=false \ No newline at end of file diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/UserAuthenticationFilter.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/UserAuthenticationFilter.java deleted file mode 100644 index 8a2de41f7..000000000 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/UserAuthenticationFilter.java +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.eclipse.hawkbit.im.authentication; - -import java.io.IOException; - -import jakarta.servlet.Filter; -import jakarta.servlet.FilterChain; -import jakarta.servlet.FilterConfig; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; - -/** - * Filter to integrate into the SP security filter-chain. The filter is called - * in any remote call through HTTP except the SP login screen. E.g. using the SP - * REST-API. To authenticate user e.g. using Basic-Authentication implement the - * {@link #doFilter(jakarta.servlet.ServletRequest, jakarta.servlet.ServletResponse, jakarta.servlet.FilterChain)} - * method. - * - * - * - */ -public interface UserAuthenticationFilter { - - /** - * @see Filter#init(FilterConfig) - * - * @param filterConfig - * the filter config - */ - void init(FilterConfig filterConfig) throws ServletException; - - /** - * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain) - * - * @param request - * the servlet request - * @param response - * the servlet response - * @param chain - * the filterchain - * @throws IOException - * cannot read from request - * @throws ServletException - * servlet exception - */ - // this declaration of multiple checked exception is necessary so it's - // aligned with the servlet API. - @SuppressWarnings("squid:S1160") - void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException; - - /** - * @see Filter#destroy() - */ - void destroy(); - -} diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/HawkbitSecurityProperties.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/HawkbitSecurityProperties.java index a01c4f8ed..7fd940c0c 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/HawkbitSecurityProperties.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/HawkbitSecurityProperties.java @@ -49,6 +49,10 @@ public class HawkbitSecurityProperties { * Basic authentication realm, see https://tools.ietf.org/html/rfc2617#page-3 . */ private String basicRealm = "hawkBit"; + /** + * If to allow http authentication when there is OAuth2 authentication enabled. + */ + private boolean allowHttpBasicOnOAuthEnabled = false; /** * Security configuration related to CORS.