This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch 3_0_X
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/3_0_X by this push:
     new 9e411ebf5f [SYNCOPE-1900] Supporting OIDC Back-Channel Logout for 
Console and Enduser (#1148)
9e411ebf5f is described below

commit 9e411ebf5f5676602dcafe4a325cf403b7bada7c
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Thu Jul 31 15:30:23 2025 +0200

    [SYNCOPE-1900] Supporting OIDC Back-Channel Logout for Console and Enduser 
(#1148)
---
 .../syncope/core/logic/AccessTokenLogic.java       |  4 +-
 .../api/data/AccessTokenDataBinder.java            |  3 +-
 .../java/data/AccessTokenDataBinderImpl.java       |  4 +-
 .../spring/security/SyncopeJWTSSOProvider.java     | 28 ++++++------
 .../commons/resources/oidcc4ui/LogoutResource.java | 45 +++++++++++++++++-
 .../syncope/common/lib/oidc/OIDCConstants.java     |  2 +
 .../apache/syncope/core/logic/OIDCC4UILogic.java   | 53 ++++++++++++++++++----
 .../syncope/core/logic/OIDCC4UILogicContext.java   |  3 ++
 .../syncope/core/logic/oidc/OIDCClientCache.java   | 35 +++++++++-----
 .../common/rest/api/service/OIDCC4UIService.java   | 13 ++++++
 .../core/rest/cxf/service/OIDCC4UIServiceImpl.java | 11 +++--
 .../apache/syncope/core/logic/SAML2SP4UILogic.java |  4 +-
 .../org/apache/syncope/fit/ui/OIDCC4UIITCase.java  | 10 ++--
 pom.xml                                            |  4 +-
 wa/starter/src/main/resources/wa.properties        |  5 +-
 15 files changed, 170 insertions(+), 54 deletions(-)

diff --git 
a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AccessTokenLogic.java
 
b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AccessTokenLogic.java
index 936c13a3ca..56d2063e2e 100644
--- 
a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AccessTokenLogic.java
+++ 
b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AccessTokenLogic.java
@@ -22,6 +22,7 @@ import java.lang.reflect.Method;
 import java.time.OffsetDateTime;
 import java.util.Collections;
 import java.util.List;
+import java.util.Optional;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.SyncopeClientException;
@@ -82,8 +83,9 @@ public class AccessTokenLogic extends 
AbstractTransactionalLogic<AccessTokenTO>
         }
 
         return binder.create(
+                Optional.empty(),
                 AuthContextUtils.getUsername(),
-                Collections.<String, Object>emptyMap(),
+                Collections.emptyMap(),
                 getAuthorities(),
                 false);
     }
diff --git 
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AccessTokenDataBinder.java
 
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AccessTokenDataBinder.java
index a487085773..a24bc035b7 100644
--- 
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AccessTokenDataBinder.java
+++ 
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AccessTokenDataBinder.java
@@ -20,6 +20,7 @@ package org.apache.syncope.core.provisioning.api.data;
 
 import java.time.OffsetDateTime;
 import java.util.Map;
+import java.util.Optional;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.to.AccessTokenTO;
 import org.apache.syncope.core.persistence.api.entity.AccessToken;
@@ -30,7 +31,7 @@ public interface AccessTokenDataBinder {
             String tokenId, String subject, long duration, Map<String, Object> 
claims);
 
     Pair<String, OffsetDateTime> create(
-            String subject, Map<String, Object> claims, byte[] authorities, 
boolean replace);
+            Optional<String> key, String subject, Map<String, Object> claims, 
byte[] authorities, boolean replace);
 
     Pair<String, OffsetDateTime> update(AccessToken accessToken, byte[] 
authorities);
 
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AccessTokenDataBinderImpl.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AccessTokenDataBinderImpl.java
index 053b4ffb0f..c1503c5a8e 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AccessTokenDataBinderImpl.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AccessTokenDataBinderImpl.java
@@ -26,6 +26,7 @@ import java.text.ParseException;
 import java.time.OffsetDateTime;
 import java.util.Date;
 import java.util.Map;
+import java.util.Optional;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
 import org.apache.syncope.common.lib.SyncopeClientException;
@@ -130,6 +131,7 @@ public class AccessTokenDataBinderImpl implements 
AccessTokenDataBinder {
 
     @Override
     public Pair<String, OffsetDateTime> create(
+            final Optional<String> key,
             final String subject,
             final Map<String, Object> claims,
             final byte[] authorities,
@@ -139,7 +141,7 @@ public class AccessTokenDataBinderImpl implements 
AccessTokenDataBinder {
         if (accessToken == null) {
             // no AccessToken found: create new
             accessToken = entityFactory.newEntity(AccessToken.class);
-            
accessToken.setKey(SecureRandomUtils.generateRandomUUID().toString());
+            accessToken.setKey(key.orElseGet(() -> 
SecureRandomUtils.generateRandomUUID().toString()));
 
             accessToken = replace(subject, claims, authorities, accessToken);
         } else if (replace
diff --git 
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeJWTSSOProvider.java
 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeJWTSSOProvider.java
index ecf4fc431a..c77123bccf 100644
--- 
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeJWTSSOProvider.java
+++ 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeJWTSSOProvider.java
@@ -25,6 +25,7 @@ import com.nimbusds.jose.JWSHeader;
 import com.nimbusds.jose.jca.JCAContext;
 import com.nimbusds.jose.util.Base64URL;
 import com.nimbusds.jwt.JWTClaimsSet;
+import java.util.Optional;
 import java.util.Set;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.types.CipherAlgorithm;
@@ -36,6 +37,7 @@ import 
org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
 import org.apache.syncope.core.spring.security.jws.AccessTokenJWSVerifier;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import 
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
 import org.springframework.transaction.annotation.Transactional;
 
 /**
@@ -94,22 +96,22 @@ public class SyncopeJWTSSOProvider implements 
JWTSSOProvider {
     @Transactional(readOnly = true)
     @Override
     public Pair<User, Set<SyncopeGrantedAuthority>> resolve(final JWTClaimsSet 
jwtClaims) {
-        User user = userDAO.findByUsername(jwtClaims.getSubject());
+        AccessToken accessToken = 
Optional.ofNullable(accessTokenDAO.find(jwtClaims.getJWTID())).
+                orElseThrow(() -> new 
AuthenticationCredentialsNotFoundException(
+                "Could not find an Access Token for JWT " + 
jwtClaims.getJWTID()));
+
         Set<SyncopeGrantedAuthority> authorities = Set.of();
-        if (user != null) {
-            AccessToken accessToken = 
accessTokenDAO.find(jwtClaims.getJWTID());
-            if (accessToken != null && accessToken.getAuthorities() != null) {
-                try {
-                    authorities = POJOHelper.deserialize(
-                            ENCRYPTOR.decode(new 
String(accessToken.getAuthorities()), CipherAlgorithm.AES),
-                            new TypeReference<>() {
-                    });
-                } catch (Throwable t) {
-                    LOG.error("Could not read stored authorities", t);
-                }
+        if (accessToken.getAuthorities() != null) {
+            try {
+                authorities = POJOHelper.deserialize(
+                        ENCRYPTOR.decode(new 
String(accessToken.getAuthorities()), CipherAlgorithm.AES),
+                        new TypeReference<>() {
+                });
+            } catch (Throwable t) {
+                LOG.error("Could not read stored authorities", t);
             }
         }
 
-        return Pair.of(user, authorities);
+        return Pair.of(userDAO.findByUsername(jwtClaims.getSubject()), 
authorities);
     }
 }
diff --git 
a/ext/oidcc4ui/client-common-ui/src/main/java/org/apache/syncope/client/ui/commons/resources/oidcc4ui/LogoutResource.java
 
b/ext/oidcc4ui/client-common-ui/src/main/java/org/apache/syncope/client/ui/commons/resources/oidcc4ui/LogoutResource.java
index 2a602b7169..8fa8c7177f 100644
--- 
a/ext/oidcc4ui/client-common-ui/src/main/java/org/apache/syncope/client/ui/commons/resources/oidcc4ui/LogoutResource.java
+++ 
b/ext/oidcc4ui/client-common-ui/src/main/java/org/apache/syncope/client/ui/commons/resources/oidcc4ui/LogoutResource.java
@@ -18,19 +18,62 @@
  */
 package org.apache.syncope.client.ui.commons.resources.oidcc4ui;
 
+import java.io.IOException;
+import java.util.Optional;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.apache.syncope.client.ui.commons.BaseSession;
+import org.apache.syncope.common.lib.oidc.OIDCConstants;
+import org.apache.syncope.common.rest.api.service.OIDCC4UIService;
 import org.apache.wicket.RestartResponseException;
+import org.apache.wicket.Session;
 import org.apache.wicket.markup.html.WebPage;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 import org.apache.wicket.request.resource.AbstractResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public abstract class LogoutResource extends AbstractResource {
 
     private static final long serialVersionUID = 273797583932923564L;
 
+    protected static final Logger LOG = 
LoggerFactory.getLogger(LogoutResource.class);
+
     protected abstract Class<? extends WebPage> getLogoutPageClass();
 
     @Override
     protected ResourceResponse newResourceResponse(final Attributes 
attributes) {
-        throw new RestartResponseException(getLogoutPageClass(), new 
PageParameters());
+        HttpServletRequest request = (HttpServletRequest) 
attributes.getRequest().getContainerRequest();
+
+        // if no logout token was found, complete RP-initated logout
+        // otherwise, proceed with back-channel logout for the provided token
+        String logoutToken = 
Optional.ofNullable(request.getParameter(OIDCConstants.LOGOUT_TOKEN)).
+                orElseThrow(() -> new 
RestartResponseException(getLogoutPageClass(), new PageParameters()));
+
+        OIDCC4UIService service = 
BaseSession.class.cast(Session.get()).getAnonymousService(OIDCC4UIService.class);
+
+        ResourceResponse response = new ResourceResponse();
+        response.getHeaders().addHeader(HttpHeaders.CACHE_CONTROL, "no-cache, 
no-store");
+        response.getHeaders().addHeader("Pragma", "no-cache");
+        try {
+            service.backChannelLogout(logoutToken, 
request.getRequestURL().toString());
+
+            response.setStatusCode(Response.Status.OK.getStatusCode());
+        } catch (Exception e) {
+            LOG.error("While requesting back-channel logout for token {}", 
logoutToken, e);
+
+            
response.setStatusCode(Response.Status.BAD_REQUEST.getStatusCode());
+            response.setContentType(MediaType.APPLICATION_JSON);
+            response.setWriteCallback(new WriteCallback() {
+
+                @Override
+                public void writeData(final Attributes atrbts) throws 
IOException {
+                    atrbts.getResponse().write("{\"error\": \"" + 
e.getMessage() + "\"}");
+                }
+            });
+        }
+        return response;
     }
 }
diff --git 
a/ext/oidcc4ui/common-lib/src/main/java/org/apache/syncope/common/lib/oidc/OIDCConstants.java
 
b/ext/oidcc4ui/common-lib/src/main/java/org/apache/syncope/common/lib/oidc/OIDCConstants.java
index 7096ae1919..9de52e994b 100644
--- 
a/ext/oidcc4ui/common-lib/src/main/java/org/apache/syncope/common/lib/oidc/OIDCConstants.java
+++ 
b/ext/oidcc4ui/common-lib/src/main/java/org/apache/syncope/common/lib/oidc/OIDCConstants.java
@@ -26,6 +26,8 @@ public final class OIDCConstants {
 
     public static final String OP = "op";
 
+    public static final String LOGOUT_TOKEN = "logout_token";
+
     private OIDCConstants() {
         // private constructor for static utility class
     }
diff --git 
a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogic.java
 
b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogic.java
index 5b2ad68e15..cf3f452669 100644
--- 
a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogic.java
+++ 
b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogic.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.logic;
 
+import com.nimbusds.jwt.JWT;
 import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.oauth2.sdk.AuthorizationCode;
@@ -44,6 +45,7 @@ import org.apache.syncope.core.logic.oidc.NoOpSessionStore;
 import org.apache.syncope.core.logic.oidc.OIDCC4UIContext;
 import org.apache.syncope.core.logic.oidc.OIDCClientCache;
 import org.apache.syncope.core.logic.oidc.OIDCUserManager;
+import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 import org.apache.syncope.core.persistence.api.dao.OIDCC4UIProviderDAO;
 import org.apache.syncope.core.persistence.api.entity.OIDCC4UIProvider;
@@ -54,6 +56,7 @@ import 
org.apache.syncope.core.spring.security.AuthDataAccessor;
 import org.apache.syncope.core.spring.security.Encryptor;
 import org.pac4j.core.context.WebContext;
 import org.pac4j.core.exception.http.WithLocationAction;
+import org.pac4j.core.util.Pac4jConstants;
 import org.pac4j.oidc.client.OidcClient;
 import org.pac4j.oidc.config.OidcConfiguration;
 import org.pac4j.oidc.credentials.OidcCredentials;
@@ -79,6 +82,8 @@ public class OIDCC4UILogic extends 
AbstractTransactionalLogic<EntityTO> {
 
     protected final OIDCC4UIProviderDAO opDAO;
 
+    protected final AccessTokenDAO accessTokenDAO;
+
     protected final OIDCUserManager userManager;
 
     public OIDCC4UILogic(
@@ -87,6 +92,7 @@ public class OIDCC4UILogic extends 
AbstractTransactionalLogic<EntityTO> {
             final AuthDataAccessor authDataAccessor,
             final AccessTokenDataBinder accessTokenDataBinder,
             final OIDCC4UIProviderDAO opDAO,
+            final AccessTokenDAO accessTokenDAO,
             final OIDCUserManager userManager) {
 
         this.oidcClientCacheLogin = oidcClientCacheLogin;
@@ -94,6 +100,7 @@ public class OIDCC4UILogic extends 
AbstractTransactionalLogic<EntityTO> {
         this.authDataAccessor = authDataAccessor;
         this.accessTokenDataBinder = accessTokenDataBinder;
         this.opDAO = opDAO;
+        this.accessTokenDAO = accessTokenDAO;
         this.userManager = userManager;
     }
 
@@ -102,7 +109,14 @@ public class OIDCC4UILogic extends 
AbstractTransactionalLogic<EntityTO> {
             final OIDCC4UIProvider op,
             final String callbackUrl) {
 
-        return oidcClientCache.get(op.getName()).orElseGet(() -> 
oidcClientCache.add(op, callbackUrl));
+        return oidcClientCache.get(op.getName()).
+                map(oidcClient -> {
+                    Optional.ofNullable(callbackUrl).
+                            filter(c -> 
!c.equals(oidcClient.getCallbackUrl())).
+                            ifPresent(oidcClient::setCallbackUrl);
+                    return oidcClient;
+                }).
+                orElseGet(() -> oidcClientCache.add(op, callbackUrl));
     }
 
     @PreAuthorize("hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
@@ -159,8 +173,9 @@ public class OIDCC4UILogic extends 
AbstractTransactionalLogic<EntityTO> {
 
             oidcClient.getAuthenticator().validate(credentials, new 
OIDCC4UIContext(), NoOpSessionStore.INSTANCE);
 
-            idToken = credentials.getIdToken().getJWTClaimsSet();
-            idTokenHint = credentials.getIdToken().serialize();
+            JWT jwt = credentials.getIdToken();
+            idToken = jwt.getJWTClaimsSet();
+            idTokenHint = jwt.serialize();
         } catch (Exception e) {
             LOG.error("While validating Token Response", e);
             SyncopeClientException sce = 
SyncopeClientException.build(ClientExceptionType.Unknown);
@@ -254,8 +269,12 @@ public class OIDCC4UILogic extends 
AbstractTransactionalLogic<EntityTO> {
             LOG.error("Could not fetch authorities", e);
         }
 
-        Pair<String, OffsetDateTime> accessTokenInfo =
-                accessTokenDataBinder.create(loginResp.getUsername(), claims, 
authorities, true);
+        Pair<String, OffsetDateTime> accessTokenInfo = 
accessTokenDataBinder.create(
+                
Optional.ofNullable(idToken.getClaim(Pac4jConstants.OIDC_CLAIM_SESSIONID)).map(Object::toString),
+                loginResp.getUsername(),
+                claims,
+                authorities,
+                true);
         loginResp.setAccessToken(accessTokenInfo.getLeft());
         loginResp.setAccessTokenExpiryTime(accessTokenInfo.getRight());
 
@@ -274,11 +293,11 @@ public class OIDCC4UILogic extends 
AbstractTransactionalLogic<EntityTO> {
             sce.getElements().add(e.getMessage());
             throw sce;
         }
+        String opName = (String) claimsSet.getClaim(JWT_CLAIM_OP_NAME);
 
         // 1. look for OidcClient
-        OIDCC4UIProvider op = Optional.ofNullable(opDAO.findByName((String) 
claimsSet.getClaim(JWT_CLAIM_OP_NAME))).
-                orElseThrow(() -> new NotFoundException(""
-                + "OIDC Provider '" + claimsSet.getClaim(JWT_CLAIM_OP_NAME) + 
'\''));
+        OIDCC4UIProvider op = Optional.ofNullable(opDAO.findByName(opName)).
+                orElseThrow(() -> new NotFoundException("OIDC Provider '" + 
opName + '\''));
         OidcClient oidcClient = getOidcClient(oidcClientCacheLogout, op, 
redirectURI);
 
         // 2. create OIDCRequest
@@ -302,6 +321,24 @@ public class OIDCC4UILogic extends 
AbstractTransactionalLogic<EntityTO> {
         return logoutRequest;
     }
 
+    @PreAuthorize("hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
+    public void backChannelLogout(final String logoutToken, final String 
redirectURI) {
+        // 0. parse the logout token
+        JWTClaimsSet claimsSet;
+        try {
+            SignedJWT jwt = SignedJWT.parse(logoutToken);
+            claimsSet = jwt.getJWTClaimsSet();
+        } catch (ParseException e) {
+            SyncopeClientException sce = 
SyncopeClientException.build(ClientExceptionType.InvalidAccessToken);
+            sce.getElements().add(e.getMessage());
+            throw sce;
+        }
+
+        // 1. delete the JWT
+        
Optional.ofNullable(claimsSet.getClaim(Pac4jConstants.OIDC_CLAIM_SESSIONID)).
+                map(String.class::cast).ifPresent(accessTokenDAO::delete);
+    }
+
     @Override
     protected EntityTO resolveReference(
             final Method method, final Object... args) throws 
UnresolvedReferenceException {
diff --git 
a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogicContext.java
 
b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogicContext.java
index a17de0b364..5b1bb6a584 100644
--- 
a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogicContext.java
+++ 
b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogicContext.java
@@ -21,6 +21,7 @@ package org.apache.syncope.core.logic;
 import org.apache.syncope.core.logic.init.OIDCC4UILoader;
 import org.apache.syncope.core.logic.oidc.OIDCClientCache;
 import org.apache.syncope.core.logic.oidc.OIDCUserManager;
+import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
 import org.apache.syncope.core.persistence.api.dao.ImplementationDAO;
 import org.apache.syncope.core.persistence.api.dao.OIDCC4UIProviderDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
@@ -89,6 +90,7 @@ public class OIDCC4UILogicContext {
             final AuthDataAccessor authDataAccessor,
             final AccessTokenDataBinder accessTokenDataBinder,
             final OIDCC4UIProviderDAO opDAO,
+            final AccessTokenDAO accessTokenDAO,
             final OIDCUserManager userManager) {
 
         return new OIDCC4UILogic(
@@ -97,6 +99,7 @@ public class OIDCC4UILogicContext {
                 authDataAccessor,
                 accessTokenDataBinder,
                 opDAO,
+                accessTokenDAO,
                 userManager);
     }
 
diff --git 
a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCClientCache.java
 
b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCClientCache.java
index dbda7839b6..503860d17c 100644
--- 
a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCClientCache.java
+++ 
b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCClientCache.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.logic.oidc;
 
+import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.oauth2.sdk.ParseException;
 import com.nimbusds.oauth2.sdk.id.Issuer;
 import com.nimbusds.openid.connect.sdk.SubjectType;
@@ -32,7 +33,6 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
 import java.util.function.Function;
-import java.util.stream.Collectors;
 import org.apache.syncope.common.lib.to.OIDCC4UIProviderTO;
 import org.apache.syncope.core.persistence.api.entity.OIDCC4UIProvider;
 import org.pac4j.core.http.callback.NoParameterCallbackUrlResolver;
@@ -51,15 +51,21 @@ public class OIDCClientCache {
     protected static final Function<String, String> DISCOVERY_URI =
             issuer -> issuer + "/.well-known/openid-configuration";
 
-    public static void importMetadata(final OIDCC4UIProviderTO opTO)
+    protected static OIDCProviderMetadata fetchMetadata(final String issuer)
             throws IOException, InterruptedException, ParseException {
 
-        String discoveryDocumentURI = DISCOVERY_URI.apply(opTO.getIssuer());
+        String discoveryDocumentURI = DISCOVERY_URI.apply(issuer);
         HttpResponse<String> response = HttpClient.newBuilder().build().send(
                 
HttpRequest.newBuilder(URI.create(discoveryDocumentURI)).GET().build(),
                 HttpResponse.BodyHandlers.ofString());
 
-        OIDCProviderMetadata metadata = 
OIDCProviderMetadata.parse(response.body());
+        return OIDCProviderMetadata.parse(response.body());
+    }
+
+    public static void importMetadata(final OIDCC4UIProviderTO opTO)
+            throws IOException, InterruptedException, ParseException {
+
+        OIDCProviderMetadata metadata = fetchMetadata(opTO.getIssuer());
 
         opTO.setIssuer(
                 
Optional.ofNullable(metadata.getIssuer()).map(Issuer::getValue).orElse(null));
@@ -83,6 +89,12 @@ public class OIDCClientCache {
     }
 
     public OidcClient add(final OIDCC4UIProvider op, final String callbackUrl) 
{
+        OidcConfiguration cfg = new OidcConfiguration();
+        cfg.setClientId(op.getClientID());
+        cfg.setSecret(op.getClientSecret());
+        cfg.setScope(String.join(" ", op.getScopes()));
+        cfg.setUseNonce(false);
+
         OIDCProviderMetadata metadata = new OIDCProviderMetadata(
                 new Issuer(op.getIssuer()),
                 List.of(SubjectType.PUBLIC),
@@ -95,14 +107,15 @@ public class OIDCClientCache {
                 
Optional.ofNullable(op.getUserinfoEndpoint()).map(URI::create).orElse(null));
         metadata.setEndSessionEndpointURI(
                 
Optional.ofNullable(op.getEndSessionEndpoint()).map(URI::create).orElse(null));
-
-        OidcConfiguration cfg = new OidcConfiguration();
-        cfg.setClientId(op.getClientID());
-        cfg.setSecret(op.getClientSecret());
+        if (op.getHasDiscovery()) {
+            try {
+                
metadata.setIDTokenJWSAlgs(fetchMetadata(op.getIssuer()).getIDTokenJWSAlgs());
+            } catch (Exception e) {
+                LOG.error("While fetching OIDC metadata for issuer {}", 
op.getIssuer(), e);
+                metadata.setIDTokenJWSAlgs(List.of(JWSAlgorithm.HS256));
+            }
+        }
         cfg.setProviderMetadata(metadata);
-        cfg.setScope(op.getScopes().stream().collect(Collectors.joining(" ")));
-        cfg.setUseNonce(false);
-        cfg.setLogoutHandler(new NoOpLogoutHandler());
 
         OidcClient client = new OidcClient(cfg);
         client.setName(op.getName());
diff --git 
a/ext/oidcc4ui/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/OIDCC4UIService.java
 
b/ext/oidcc4ui/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/OIDCC4UIService.java
index 19f8ec371f..d658df5862 100644
--- 
a/ext/oidcc4ui/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/OIDCC4UIService.java
+++ 
b/ext/oidcc4ui/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/OIDCC4UIService.java
@@ -85,4 +85,17 @@ public interface OIDCC4UIService extends JAXRSService {
     @Path("logout")
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     OIDCRequest createLogoutRequest(@QueryParam(OIDCConstants.REDIRECT_URI) 
String redirectURI);
+
+    /**
+     * Removes the JWT matching the provided OIDC logout token.
+     *
+     * @param logoutToken logout token
+     * @param redirectURI redirect URI
+     */
+    @POST
+    @Path("backChannelLogout")
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    void backChannelLogout(
+            @QueryParam(OIDCConstants.LOGOUT_TOKEN) String logoutToken,
+            @QueryParam(OIDCConstants.REDIRECT_URI) String redirectURI);
 }
diff --git 
a/ext/oidcc4ui/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/OIDCC4UIServiceImpl.java
 
b/ext/oidcc4ui/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/OIDCC4UIServiceImpl.java
index f116fbd381..250aa316be 100644
--- 
a/ext/oidcc4ui/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/OIDCC4UIServiceImpl.java
+++ 
b/ext/oidcc4ui/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/OIDCC4UIServiceImpl.java
@@ -47,16 +47,17 @@ public class OIDCC4UIServiceImpl extends AbstractService 
implements OIDCC4UIServ
 
     @Override
     public OIDCRequest createLogoutRequest(final String redirectURI) {
-        return logic.createLogoutRequest(getAccessToken(), redirectURI);
-    }
-
-    private String getAccessToken() {
         String auth = 
messageContext.getHttpHeaders().getHeaderString(HttpHeaders.AUTHORIZATION);
         String[] parts = Optional.ofNullable(auth).map(s -> s.split(" 
")).orElse(null);
         if (parts == null || parts.length != 2 || !"Bearer".equals(parts[0])) {
             return null;
         }
 
-        return parts[1];
+        return logic.createLogoutRequest(parts[1], redirectURI);
+    }
+
+    @Override
+    public void backChannelLogout(final String logoutToken, final String 
redirectURI) {
+        logic.backChannelLogout(logoutToken, redirectURI);
     }
 }
diff --git 
a/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/SAML2SP4UILogic.java
 
b/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/SAML2SP4UILogic.java
index 8152c8deb0..4704420504 100644
--- 
a/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/SAML2SP4UILogic.java
+++ 
b/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/SAML2SP4UILogic.java
@@ -439,8 +439,8 @@ public class SAML2SP4UILogic extends 
AbstractSAML2SP4UILogic {
             LOG.error("Could not fetch authorities", e);
         }
 
-        Pair<String, OffsetDateTime> accessTokenInfo =
-                accessTokenDataBinder.create(loginResp.getUsername(), claims, 
authorities, true);
+        Pair<String, OffsetDateTime> accessTokenInfo = 
accessTokenDataBinder.create(
+                Optional.of(loginResp.getSessionIndex()), 
loginResp.getUsername(), claims, authorities, true);
         loginResp.setAccessToken(accessTokenInfo.getLeft());
         loginResp.setAccessTokenExpiryTime(accessTokenInfo.getRight());
 
diff --git 
a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java 
b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java
index 831703254f..52171903fc 100644
--- 
a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java
+++ 
b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java
@@ -51,6 +51,7 @@ import org.apache.syncope.common.lib.to.Item;
 import org.apache.syncope.common.lib.to.OIDCC4UIProviderTO;
 import org.apache.syncope.common.lib.to.OIDCRPClientAppTO;
 import org.apache.syncope.common.lib.types.ClientAppType;
+import org.apache.syncope.common.lib.types.LogoutType;
 import org.apache.syncope.common.lib.types.OIDCResponseType;
 import org.apache.syncope.common.lib.types.OIDCSubjectType;
 import org.apache.syncope.common.rest.api.RESTHeaders;
@@ -90,6 +91,7 @@ public class OIDCC4UIITCase extends AbstractUIITCase {
         clientApp.getRedirectUris().add(baseAddress + 
OIDCC4UIConstants.URL_CONTEXT + "/code-consumer");
         clientApp.setSignIdToken(true);
         clientApp.setJwtAccessToken(true);
+        clientApp.setLogoutType(LogoutType.BACK_CHANNEL);
         clientApp.setLogoutUri(baseAddress + OIDCC4UIConstants.URL_CONTEXT + 
"/logout");
         clientApp.getSupportedResponseTypes().addAll(
                 Set.of(OIDCResponseType.CODE, OIDCResponseType.ID_TOKEN_TOKEN, 
OIDCResponseType.TOKEN));
@@ -134,11 +136,7 @@ public class OIDCC4UIITCase extends AbstractUIITCase {
             cas.setClientSecret(appName);
 
             cas.setIssuer(WA_ADDRESS + "/oidc");
-            cas.setAuthorizationEndpoint(cas.getIssuer() + "/authorize");
-            cas.setTokenEndpoint(cas.getIssuer() + "/accessToken");
-            cas.setJwksUri(cas.getIssuer() + "/jwks");
-            cas.setUserinfoEndpoint(cas.getIssuer() + "/profile");
-            cas.setEndSessionEndpoint(cas.getIssuer() + "/logout");
+            cas.setHasDiscovery(true);
 
             cas.getScopes().addAll(OIDCScopeConstants.ALL_STANDARD_SCOPES);
             cas.getScopes().add("syncope");
@@ -177,7 +175,7 @@ public class OIDCC4UIITCase extends AbstractUIITCase {
             item.setExtAttrName("name");
             cas.add(item);
 
-            OIDCC4UI_PROVIDER_SERVICE.create(cas);
+            OIDCC4UI_PROVIDER_SERVICE.createFromDiscovery(cas);
         }
     }
 
diff --git a/pom.xml b/pom.xml
index c2edcfecb0..3391412a24 100644
--- a/pom.xml
+++ b/pom.xml
@@ -508,10 +508,10 @@ under the License.
     <docker.mariadb.version>11</docker.mariadb.version>
 
     <jdbc.postgresql.version>42.7.7</jdbc.postgresql.version>
-    <jdbc.mysql.version>9.2.0</jdbc.mysql.version>
+    <jdbc.mysql.version>9.4.0</jdbc.mysql.version>
     <jdbc.mariadb.version>3.5.4</jdbc.mariadb.version>
     <jdbc.mssql.version>12.6.1.jre11</jdbc.mssql.version>
-    <jdbc.oracle.version>23.8.0.25.04</jdbc.oracle.version>
+    <jdbc.oracle.version>23.9.0.25.07</jdbc.oracle.version>
 
     <bundles.directory>${project.build.directory}/bundles</bundles.directory>
 
diff --git a/wa/starter/src/main/resources/wa.properties 
b/wa/starter/src/main/resources/wa.properties
index ea2a6ee0cd..ff887f8c84 100644
--- a/wa/starter/src/main/resources/wa.properties
+++ b/wa/starter/src/main/resources/wa.properties
@@ -50,9 +50,8 @@ cas.service-registry.schedule.start-delay=PT30S
 
 cas.events.core.enabled=false
 
-##
-# Allow configuration classes to override bean definitions from Spring Boot
-#
+cas.slo.disabled=false
+
 spring.main.allow-bean-definition-overriding=true
 spring.main.lazy-initialization=false
 

Reply via email to