Improve oauth2 (#3014)

* feat: add custom header to oauth2 req

* fix: current.getClass() raise NPE

* fix: use access token instead of id token

* fix: missing dependency

* feat: add oauth2 login from swagger-ui

* docs: update oauth2 configuration
This commit is contained in:
Florian BEZANNIER
2026-05-11 13:50:47 +02:00
committed by GitHub
parent 394048a583
commit 8d83218dc8
7 changed files with 199 additions and 57 deletions

View File

@@ -29,6 +29,7 @@ import org.eclipse.hawkbit.sdk.HawkbitClient;
import org.eclipse.hawkbit.sdk.HawkbitServer;
import org.eclipse.hawkbit.sdk.Tenant;
import org.eclipse.hawkbit.ui.view.util.Utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@@ -41,7 +42,10 @@ import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
@Slf4j
@Theme("hawkbit")
@@ -56,22 +60,32 @@ public class HawkbitUiApp implements AppShellConfigurator {
private static final long serialVersionUID = 1L;
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final RequestInterceptor AUTHORIZATION = requestTemplate -> {
final Authentication authentication = Objects.requireNonNull(
SecurityContextHolder.getContext().getAuthentication(), "No authentication available in security context!");
final Object principal = Objects.requireNonNull(authentication.getPrincipal(), "User is null!");
if (principal instanceof OidcUser oidcUser) {
requestTemplate.header(
AUTHORIZATION_HEADER,
"Bearer " + oidcUser.getIdToken().getTokenValue());
} else {
final String user = String.valueOf(principal);
final Object pass = Objects.requireNonNull(authentication.getCredentials(), "Password is not available!");
requestTemplate.header(
AUTHORIZATION_HEADER,
"Basic " + Base64.getEncoder().encodeToString((user + ":" + pass).getBytes(ISO_8859_1)));
}
};
private static RequestInterceptor authorizationInterceptor(final OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager) {
return requestTemplate -> {
final Authentication authentication = Objects.requireNonNull(
SecurityContextHolder.getContext().getAuthentication(), "No authentication available in security context!");
if (authentication instanceof OAuth2AuthenticationToken oauth2Token) {
// line from /org/springframework/security/oauth2/client/web/client/OAuth2ClientHttpRequestInterceptor.java#authorizeClient
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId(oauth2Token
.getAuthorizedClientRegistrationId()).principal(authentication).build();
OAuth2AuthorizedClient authorizedClient = oAuth2AuthorizedClientManager.authorize(authorizeRequest);
if (authorizedClient != null) {
requestTemplate.header(AUTHORIZATION_HEADER, "Bearer " + authorizedClient.getAccessToken().getTokenValue());
} else {
log.warn("No authorized client found for principal {} — request will be sent without Authorization header", oauth2Token
.getName());
}
} else {
final Object principal = Objects.requireNonNull(authentication.getPrincipal(), "User is null!");
final String user = String.valueOf(principal);
final Object pass = Objects.requireNonNull(authentication.getCredentials(), "Password is not available!");
requestTemplate.header(
AUTHORIZATION_HEADER,
"Basic " + Base64.getEncoder().encodeToString((user + ":" + pass).getBytes(ISO_8859_1)));
}
};
}
private static final ErrorDecoder DEFAULT_ERROR_DECODER = new ErrorDecoder.Default();
private static final ErrorDecoder ERROR_DECODER = (methodKey, response) -> {
@@ -85,12 +99,14 @@ public class HawkbitUiApp implements AppShellConfigurator {
}
@Bean
HawkbitClient hawkbitClient(final HawkbitServer hawkBitServer) {
HawkbitClient hawkbitClient(final HawkbitServer hawkBitServer,
@Autowired(required = false) final OAuth2AuthorizedClientManager authorizedClientManager) {
final RequestInterceptor authorization = authorizationInterceptor(authorizedClientManager);
return new HawkbitClient(
hawkBitServer, null, null, null,
ERROR_DECODER,
(tenant, controller) -> controller == null
? AUTHORIZATION
? authorization
: HawkbitClient.DEFAULT_REQUEST_INTERCEPTOR_FN.apply(tenant, controller));
}

View File

@@ -9,6 +9,8 @@
*/
package org.eclipse.hawkbit.ui.security;
import java.util.Map;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
@@ -16,13 +18,32 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
@Configuration
@ConditionalOnProperty(prefix = "hawkbit.server.security.oauth2.client", name = "enabled")
public class Oauth2ClientConfig {
@Bean(name = "hawkbitOAuth2ClientCustomizer")
@ConditionalOnProperty(prefix = "hawkbit.server.security.oauth2.client", name = "enabled")
@ConditionalOnMissingBean(name = "hawkbitOAuth2ClientCustomizer")
Customizer<OAuth2LoginConfigurer<HttpSecurity>> defaultOAuth2ClientCustomizer() {
return Customizer.withDefaults();
}
@Bean
@ConditionalOnMissingBean
public OAuth2AuthorizationRequestResolver authorizationRequestResolver(
ClientRegistrationRepository repo,
OidcClientProperties properties) {
final Map<String, String> additionalQueryStringParams = properties.getOauth2().getClient().getAdditionalQueryStringParams();
final DefaultOAuth2AuthorizationRequestResolver resolver = new DefaultOAuth2AuthorizationRequestResolver(repo,
OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI);
resolver.setAuthorizationRequestCustomizer(
customizer -> customizer.additionalParameters(params -> params.putAll(additionalQueryStringParams)));
return resolver;
}
}

View File

@@ -9,6 +9,9 @@
*/
package org.eclipse.hawkbit.ui.security;
import java.util.HashMap;
import java.util.Map;
import lombok.Data;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -29,6 +32,7 @@ public class OidcClientProperties {
public static class Client {
private boolean enabled = false;
private Map<String, String> additionalQueryStringParams = new HashMap<>();
}
}
}
}