Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ public class SimpleUIApp implements AppShellConfigurator {

private static final Function<OAuth2TokenManager, RequestInterceptor> AUTHORIZATION = oAuth2TokenManager -> requestTemplate -> {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (oAuth2TokenManager != null && authentication instanceof OAuth2AuthenticationToken oAuth2AuthenticationToken) {
String bearerToken = oAuth2TokenManager.getToken(oAuth2AuthenticationToken);
if (authentication instanceof OAuth2AuthenticationToken authenticationToken) {
String bearerToken = oAuth2TokenManager.getToken(authenticationToken);
requestTemplate.header("Authorization", "Bearer " + bearerToken);
} else {
requestTemplate.header(
Expand Down Expand Up @@ -108,16 +108,14 @@ HawkbitMgmtClient hawkbitMgmtClient(final Tenant tenant, final HawkbitClient haw
@Bean
OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService(final HawkbitMgmtClient hawkbitClient) {
final OidcUserService delegate = new OidcUserService();
return userRequest -> {
return (userRequest) -> {
OidcUser oidcUser = delegate.loadUser(userRequest);

final OAuth2AuthenticationToken tempToken = new OAuth2AuthenticationToken(
oidcUser,
emptyList(),
userRequest.getClientRegistration().getRegistrationId()
);
final List<SimpleGrantedAuthority> grantedAuthorities =
getGrantedAuthorities(hawkbitClient, tempToken);
final List<SimpleGrantedAuthority> grantedAuthorities = getGrantedAuthorities(hawkbitClient, tempToken);
return new DefaultOidcUser(
grantedAuthorities,
oidcUser.getIdToken(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,73 +10,48 @@
package org.eclipse.hawkbit.ui.simple.security;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
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.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
import org.springframework.security.oauth2.client.endpoint.RestClientRefreshTokenTokenResponseClient;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.stereotype.Component;

import java.util.Optional;

@Component
@ConditionalOnProperty(prefix = "hawkbit.server.security.oauth2.client", name = "enabled")
public class OAuth2TokenManager {

private final OAuth2AuthorizedClientService clientService;
private final OAuth2AuthorizedClientManager clientManager;
private final OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> tokenResponseClient;

OAuth2TokenManager(
final OAuth2AuthorizedClientService clientService,
final OAuth2AuthorizedClientManager clientManager
) {
this.clientService = clientService;
this.clientManager = clientManager;
this.tokenResponseClient = new RestClientRefreshTokenTokenResponseClient();
}

public String getToken(final OAuth2AuthenticationToken authentication) {
return Optional.ofNullable(authorizedToken(authentication)).orElse(
((DefaultOidcUser) authentication.getPrincipal()).getIdToken().getTokenValue()
);
}

/**
* Tries to refresh the id token if it is expired and adds it to the request.
*/
private String authorizedToken(final OAuth2AuthenticationToken authentication) {
final String currentToken = ((DefaultOidcUser) authentication.getPrincipal()).getIdToken().getTokenValue();
String registrationId = authentication.getAuthorizedClientRegistrationId();
OAuth2AuthorizeRequest request = OAuth2AuthorizeRequest.withClientRegistrationId(registrationId).principal(authentication).build();

// This ensures that there is a client already, otherwise we won't be able to call the manager for authorization
OAuth2AuthorizedClient authorizedClient = clientService.loadAuthorizedClient(registrationId, authentication.getName());
if (authorizedClient == null) return null;
if (authorizedClient == null) return currentToken;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If client isn't found why return a token ?

Copy link
Contributor Author

@zeapo zeapo Sep 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hehe, this is the tricky parts of how simple-ui rights are working. It hijacks the authentication process, hence the initial step where it fetches the rights to create the authorities, the authorizedClient does not exist.

That's why this OIDC process is much more complex than it should've been.

image Screenshot 2025-09-01 at 14 31 10


// Will ensure that the token is refreshed if needed; do not rely on it being not null as it won't be available
// during the first calls made to get the rights and generate the authorities
OAuth2AuthorizedClient refreshClient = clientManager.authorize(request);
if (refreshClient == null) return null;

// A small trick to refresh the token if it is expired; the current spring version does not refresh the ID Token when the Access Token is refreshed
// This won't be necessary after Spring Security 6.5; cf. https://github.com/spring-projects/spring-security/pull/16589
OAuth2AccessToken accessToken = refreshClient.getAccessToken();
OAuth2RefreshToken refreshToken = refreshClient.getRefreshToken();
ClientRegistration clientRegistration = refreshClient.getClientRegistration();
// if this is null, please request it via the scopes
if (refreshToken == null) return null;
OAuth2AuthorizeRequest request = OAuth2AuthorizeRequest.withClientRegistrationId(registrationId).principal(authentication).build();
// since Spring Security 6.5 this will trigger a refresh of the id token
authorizedClient = clientManager.authorize(request);
if (authorizedClient == null) return currentToken;

OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest(
clientRegistration, accessToken, refreshToken);
OAuth2AccessTokenResponse response = tokenResponseClient.getTokenResponse(refreshTokenGrantRequest);
return (String) response.getAdditionalParameters().get("id_token");
// we need to fetch the newly created context containing the matching token
SecurityContext securityContext = SecurityContextHolder.getContext();
return ((DefaultOidcUser) securityContext.getAuthentication().getPrincipal()).getIdToken().getTokenValue();
}
}