OIDC Authentication/Authorization on Management API (#2386)
* Added Oidc ressource server capabilities to mgmt api to allow users to login via identity provider. Signed-off-by: ChristianB <christian.breitwieser@blue-zone.at> * Adress review findings: - Code Style fixes - Readability improvements Signed-off-by: ChristianB <christian.breitwieser@blue-zone.at> --------- Signed-off-by: ChristianB <christian.breitwieser@blue-zone.at>
This commit is contained in:
committed by
GitHub
parent
7456e52095
commit
acaec605bd
@@ -72,6 +72,14 @@
|
|||||||
<groupId>org.springframework.security</groupId>
|
<groupId>org.springframework.security</groupId>
|
||||||
<artifactId>spring-security-aspects</artifactId>
|
<artifactId>spring-security-aspects</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-oauth2-resource-server</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-oauth2-jose</artifactId>
|
||||||
|
</dependency>
|
||||||
<!-- Spring - END -->
|
<!-- Spring - END -->
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
@@ -10,18 +10,28 @@
|
|||||||
package org.eclipse.hawkbit.autoconfigure.mgmt;
|
package org.eclipse.hawkbit.autoconfigure.mgmt;
|
||||||
|
|
||||||
import java.util.List;
|
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.im.authentication.SpPermission;
|
||||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
|
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
|
||||||
|
import org.eclipse.hawkbit.oidc.OidcProperties;
|
||||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||||
import org.eclipse.hawkbit.rest.SecurityManagedConfiguration;
|
import org.eclipse.hawkbit.rest.SecurityManagedConfiguration;
|
||||||
import org.eclipse.hawkbit.rest.security.DosFilter;
|
import org.eclipse.hawkbit.rest.security.DosFilter;
|
||||||
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
|
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
|
||||||
import org.eclipse.hawkbit.security.MdcHandler;
|
import org.eclipse.hawkbit.security.MdcHandler;
|
||||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
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.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
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.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
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.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.core.Authentication;
|
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.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.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
|
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
|
||||||
import org.springframework.security.web.session.SessionManagementFilter;
|
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.
|
* Security configuration for the REST management API.
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
|
@EnableConfigurationProperties({HawkbitSecurityProperties.class, OidcProperties.class})
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
public class MgmtSecurityConfiguration {
|
public class MgmtSecurityConfiguration {
|
||||||
|
|
||||||
private final HawkbitSecurityProperties securityProperties;
|
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.securityProperties = securityProperties;
|
||||||
|
this.oidcProperties = oidcProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -70,6 +87,69 @@ public class MgmtSecurityConfiguration {
|
|||||||
return filterRegBean;
|
return filterRegBean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class DefaultOAuth2ResourceServerCustomizer implements Customizer<OAuth2ResourceServerConfigurer<HttpSecurity>> {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
static class HawkbitJwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationToken<Jwt> {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public HawkbitJwtAuthenticationToken(Jwt jwt, TenantAwareUser user, Collection<GrantedAuthority> authorities) {
|
||||||
|
super(jwt, user, jwt, authorities);
|
||||||
|
setDetails(new TenantAwareAuthenticationDetails(user.getTenant(), false));
|
||||||
|
this.name = jwt.getSubject();
|
||||||
|
setAuthenticated(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> 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<String, Object>) current).get(part);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void customize(OAuth2ResourceServerConfigurer<HttpSecurity> 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<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
|
||||||
|
final Collection<String> resourceRoles = (Collection<String>) 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<OAuth2ResourceServerConfigurer<HttpSecurity>> defaultOAuth2ResourceServerCustomizer() {
|
||||||
|
return new DefaultOAuth2ResourceServerCustomizer();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Order(350)
|
@Order(350)
|
||||||
SecurityFilterChain filterChainREST(
|
SecurityFilterChain filterChainREST(
|
||||||
|
|||||||
@@ -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 <a href="https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/jwt.html#_specifying_the_authorization_server">Spring Documentation</a>
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
licenses/LICENSE_HEADER_TEMPLATE_BLUEZONE_25.txt
Normal file
7
licenses/LICENSE_HEADER_TEMPLATE_BLUEZONE_25.txt
Normal file
@@ -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
|
||||||
1
pom.xml
1
pom.xml
@@ -411,6 +411,7 @@
|
|||||||
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_DEVOLO_20.txt</validHeader>
|
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_DEVOLO_20.txt</validHeader>
|
||||||
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_KIWIGRID_19.txt</validHeader>
|
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_KIWIGRID_19.txt</validHeader>
|
||||||
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_ENAPTER.txt</validHeader>
|
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_ENAPTER.txt</validHeader>
|
||||||
|
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_BLUEZONE_25.txt</validHeader>
|
||||||
</validHeaders>
|
</validHeaders>
|
||||||
<excludes>
|
<excludes>
|
||||||
<exclude>.3rd-party/**</exclude>
|
<exclude>.3rd-party/**</exclude>
|
||||||
|
|||||||
Reference in New Issue
Block a user