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>
|
||||
<artifactId>spring-security-aspects</artifactId>
|
||||
</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 -->
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -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<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
|
||||
@Order(350)
|
||||
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_KIWIGRID_19.txt</validHeader>
|
||||
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_ENAPTER.txt</validHeader>
|
||||
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_BLUEZONE_25.txt</validHeader>
|
||||
</validHeaders>
|
||||
<excludes>
|
||||
<exclude>.3rd-party/**</exclude>
|
||||
|
||||
Reference in New Issue
Block a user