diff --git a/hawkbit-mgmt/hawkbit-mgmt-starter/pom.xml b/hawkbit-mgmt/hawkbit-mgmt-starter/pom.xml
index e4702d81f..8ce183e46 100644
--- a/hawkbit-mgmt/hawkbit-mgmt-starter/pom.xml
+++ b/hawkbit-mgmt/hawkbit-mgmt-starter/pom.xml
@@ -72,6 +72,14 @@
org.springframework.security
spring-security-aspects
+
+ org.springframework.security
+ spring-security-oauth2-resource-server
+
+
+ org.springframework.security
+ spring-security-oauth2-jose
+
\ No newline at end of file
diff --git a/hawkbit-mgmt/hawkbit-mgmt-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/mgmt/MgmtSecurityConfiguration.java b/hawkbit-mgmt/hawkbit-mgmt-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/mgmt/MgmtSecurityConfiguration.java
index 3156a4fad..e0e62b71a 100644
--- a/hawkbit-mgmt/hawkbit-mgmt-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/mgmt/MgmtSecurityConfiguration.java
+++ b/hawkbit-mgmt/hawkbit-mgmt-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/mgmt/MgmtSecurityConfiguration.java
@@ -10,18 +10,28 @@
package org.eclipse.hawkbit.autoconfigure.mgmt;
import java.util.List;
+import java.util.Collection;
+import java.util.Map;
+import java.util.HashSet;
+import java.util.stream.Collectors;
+import lombok.Getter;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
+import org.eclipse.hawkbit.oidc.OidcProperties;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.rest.SecurityManagedConfiguration;
import org.eclipse.hawkbit.rest.security.DosFilter;
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
import org.eclipse.hawkbit.security.MdcHandler;
import org.eclipse.hawkbit.security.SystemSecurityContext;
+import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
+import org.eclipse.hawkbit.tenancy.TenantAwareUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -33,7 +43,11 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt
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.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.security.web.session.SessionManagementFilter;
@@ -42,13 +56,16 @@ import org.springframework.security.web.session.SessionManagementFilter;
* Security configuration for the REST management API.
*/
@Configuration
+@EnableConfigurationProperties({HawkbitSecurityProperties.class, OidcProperties.class})
@EnableWebSecurity
public class MgmtSecurityConfiguration {
private final HawkbitSecurityProperties securityProperties;
+ private final OidcProperties oidcProperties;
- public MgmtSecurityConfiguration(final HawkbitSecurityProperties securityProperties) {
+ public MgmtSecurityConfiguration(final HawkbitSecurityProperties securityProperties, final OidcProperties oidcProperties) {
this.securityProperties = securityProperties;
+ this.oidcProperties = oidcProperties;
}
/**
@@ -70,6 +87,69 @@ public class MgmtSecurityConfiguration {
return filterRegBean;
}
+ public class DefaultOAuth2ResourceServerCustomizer implements Customizer> {
+
+ @Getter
+ static class HawkbitJwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationToken {
+ private static final long serialVersionUID = 1L;
+
+ private final String name;
+
+ public HawkbitJwtAuthenticationToken(Jwt jwt, TenantAwareUser user, Collection authorities) {
+ super(jwt, user, jwt, authorities);
+ setDetails(new TenantAwareAuthenticationDetails(user.getTenant(), false));
+ this.name = jwt.getSubject();
+ setAuthenticated(true);
+ }
+
+ public Map getTokenAttributes() {
+ return (this.getToken()).getClaims();
+ }
+
+ }
+
+ static Object followPathInJWTClaims(Jwt jwt, String path) {
+ final String[] parts = path.split("\\.");
+ Object current = jwt.getClaims();
+ for (final String part : parts) {
+ if (current instanceof Map) {
+ current = ((Map) current).get(part);
+ } else {
+ break;
+ }
+ }
+ return current;
+ }
+
+ @Override
+ public void customize(OAuth2ResourceServerConfigurer oauth2ResourceServerConfigurer) {
+ final String usernameClaim = oidcProperties.getOauth2().getResourceserver().getJwt().getClaim().getUsername();
+ final String rolesClaim = oidcProperties.getOauth2().getResourceserver().getJwt().getClaim().getRoles();
+ final String tenantClaim = oidcProperties.getOauth2().getResourceserver().getJwt().getClaim().getTenant();
+ oauth2ResourceServerConfigurer.jwt(jwtConfigurer -> jwtConfigurer.jwtAuthenticationConverter(jwt -> {
+
+ final String username = (String) followPathInJWTClaims(jwt, usernameClaim);
+ final String tenantName = tenantClaim == null ? "DEFAULT" : (String) followPathInJWTClaims(jwt, tenantClaim);
+ final Collection authorities = new HashSet();
+ final Collection resourceRoles = (Collection) followPathInJWTClaims(jwt, rolesClaim);
+ if (resourceRoles != null) {
+ authorities.addAll(resourceRoles.stream()
+ .map(SimpleGrantedAuthority::new)
+ .collect(Collectors.toSet()));
+ }
+ final TenantAwareUser user = new TenantAwareUser(username, username, authorities, tenantName);
+ return new HawkbitJwtAuthenticationToken(jwt, user, authorities);
+ }));
+ }
+ }
+
+ @Bean(name = "hawkbitOAuth2ResourceServerCustomizer")
+ @ConditionalOnProperty(prefix = "hawkbit.server.security.oauth2.resourceserver", name = "enabled", matchIfMissing = false)
+ @ConditionalOnMissingBean(name = "hawkbitOAuth2ResourceServerCustomizer")
+ Customizer> defaultOAuth2ResourceServerCustomizer() {
+ return new DefaultOAuth2ResourceServerCustomizer();
+ }
+
@Bean
@Order(350)
SecurityFilterChain filterChainREST(
diff --git a/hawkbit-mgmt/hawkbit-mgmt-starter/src/main/java/org/eclipse/hawkbit/oidc/OidcProperties.java b/hawkbit-mgmt/hawkbit-mgmt-starter/src/main/java/org/eclipse/hawkbit/oidc/OidcProperties.java
new file mode 100644
index 000000000..c081032a1
--- /dev/null
+++ b/hawkbit-mgmt/hawkbit-mgmt-starter/src/main/java/org/eclipse/hawkbit/oidc/OidcProperties.java
@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2025 blue-zone 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.oidc;
+
+import lombok.Data;
+import lombok.ToString;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * Configuration for hawkBit oidc resource server
+ */
+@Data
+@ToString
+@ConfigurationProperties("hawkbit.server.security")
+public class OidcProperties {
+
+ private final OidcProperties.Oauth2 oauth2 = new OidcProperties.Oauth2();
+
+ @Data
+ public static class Oauth2 {
+ private final OidcProperties.Oauth2.ResourceServer resourceserver = new OidcProperties.Oauth2.ResourceServer();
+
+ @Data
+ public static class ResourceServer {
+ private final OidcProperties.Oauth2.ResourceServer.Jwt jwt = new OidcProperties.Oauth2.ResourceServer.Jwt();
+
+ /**
+ * Indicates whether the default OAuth2 resource server configuration is enabled.
+ * Defaults to false. If false either no Oauth2 resource server is active or a hawkbitOAuth2ResourceServerCustomizer component can be used to define custom OAuth2 resource server behaviour.
+ * If true, the default spring OAuth2 resource server configuration is activated.
+ * @see Spring Documentation
+ */
+ private boolean enabled = false;
+
+ @Data
+ public static class Jwt {
+ private final OidcProperties.Oauth2.ResourceServer.Jwt.Claim claim = new OidcProperties.Oauth2.ResourceServer.Jwt.Claim();
+
+ @Data
+ public static class Claim {
+ /**
+ * Defines the claim within the JWT token that supplies the hawkbit username.
+ */
+ private String username = "preferred_username";
+
+ /**
+ * Defines the claim within the JWT token that supplies the hawkbit authorities.
+ */
+ private String roles = "roles";
+
+ /**
+ * Defines the claim within the JWT token that supplies the hawkbit tenant.
+ * If null, the DEFAULT tenant is used for every user.
+ */
+ private String tenant = null;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/licenses/LICENSE_HEADER_TEMPLATE_BLUEZONE_25.txt b/licenses/LICENSE_HEADER_TEMPLATE_BLUEZONE_25.txt
new file mode 100644
index 000000000..e1b295339
--- /dev/null
+++ b/licenses/LICENSE_HEADER_TEMPLATE_BLUEZONE_25.txt
@@ -0,0 +1,7 @@
+Copyright (c) 2025 blue-zone 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
diff --git a/pom.xml b/pom.xml
index 44a610870..818f3d564 100644
--- a/pom.xml
+++ b/pom.xml
@@ -411,6 +411,7 @@
licenses/LICENSE_HEADER_TEMPLATE_DEVOLO_20.txt
licenses/LICENSE_HEADER_TEMPLATE_KIWIGRID_19.txt
licenses/LICENSE_HEADER_TEMPLATE_ENAPTER.txt
+ licenses/LICENSE_HEADER_TEMPLATE_BLUEZONE_25.txt
.3rd-party/**