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<OAuth2ResourceServerConfigurer<HttpSecurity>>_ as configuration for OAuth. Thus it is not bound with oauth client configuration
* _OidcUserManagementAutoConfiguration_ - now registers (if conditions are met) Customizer<OAuth2ResourceServerConfigurer<HttpSecurity>> 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<OAuth2ResourceServerConfigurer<HttpSecurity>>)_ 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 <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-07-26 10:59:15 +03:00
committed by GitHub
parent 6b8917e229
commit 3a34ded4f6
5 changed files with 100 additions and 351 deletions

View File

@@ -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<OAuth2ResourceServerConfigurer<HttpSecurity>> 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<GrantedAuthority> 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<GrantedAuthority> 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
}
}
}

View File

@@ -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<DosFilter> dosFilterDDI(final HawkbitSecurityProperties securityProperties) {
protected FilterRegistrationBean<DosFilter> dosFilterDDI(final HawkbitSecurityProperties securityProperties) {
final FilterRegistrationBean<DosFilter> 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<DosFilter> dosFilterREST() {
final FilterRegistrationBean<DosFilter> 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<OAuth2ResourceServerConfigurer<HttpSecurity>> 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;
}
}

View File

@@ -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=
hawkbit.server.ddi.security.authentication.gatewaytoken.key=
hawkbit.server.download.anonymous.enabled=false

View File

@@ -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();
}

View File

@@ -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.