You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2017/06/28 14:27:27 UTC

[2/2] syncope git commit: [SYNCOPE-1129] New interface JWTSSOProvider with default impl SyncopeJWTSSOProvider + docs

[SYNCOPE-1129] New interface JWTSSOProvider with default impl SyncopeJWTSSOProvider + docs


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/5a7010bb
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/5a7010bb
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/5a7010bb

Branch: refs/heads/2_0_X
Commit: 5a7010bb378ea236038fd91578fcdafe017cf551
Parents: f6e9fa3
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Wed Jun 28 16:26:07 2017 +0200
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Wed Jun 28 16:27:13 2017 +0200

----------------------------------------------------------------------
 .../syncope/common/lib/info/PlatformInfo.java   |  9 +++
 .../apache/syncope/core/logic/SyncopeLogic.java |  1 +
 .../init/ClassPathScanImplementationLookup.java | 15 ++++
 .../persistence/api/ImplementationLookup.java   |  3 +
 .../jpa/DummyImplementationLookup.java          |  5 ++
 .../java/DummyImplementationLookup.java         |  5 ++
 .../core/spring/security/AuthDataAccessor.java  | 37 ++++++++-
 .../security/DefaultCredentialChecker.java      | 11 ++-
 .../security/JWTAuthenticationFilter.java       |  6 +-
 .../security/JWTAuthenticationProvider.java     | 12 +--
 .../core/spring/security/JWTSSOProvider.java    | 44 ++++++++++
 .../spring/security/SyncopeJWTSSOProvider.java  | 84 ++++++++++++++++++++
 .../src/main/resources/securityContext.xml      | 10 +--
 .../apache/syncope/core/logic/SAML2SPLogic.java | 30 +++++--
 .../core/reference/ITImplementationLookup.java  |  7 ++
 .../org/apache/syncope/fit/core/JWTITCase.java  | 30 +++----
 .../workingwithapachesyncope/customization.adoc |  1 +
 .../restfulservices.adoc                        | 12 +++
 18 files changed, 271 insertions(+), 51 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/5a7010bb/common/lib/src/main/java/org/apache/syncope/common/lib/info/PlatformInfo.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/info/PlatformInfo.java b/common/lib/src/main/java/org/apache/syncope/common/lib/info/PlatformInfo.java
index 9a7ac61..1f696d8 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/info/PlatformInfo.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/info/PlatformInfo.java
@@ -69,6 +69,8 @@ public class PlatformInfo extends AbstractBaseBean {
 
     private final Set<String> entitlements = new HashSet<>();
 
+    private final Set<String> jwtSSOProviders = new HashSet<>();
+
     private final Set<String> reportletConfs = new HashSet<>();
 
     private final Set<String> accountRules = new HashSet<>();
@@ -183,6 +185,13 @@ public class PlatformInfo extends AbstractBaseBean {
         return entitlements;
     }
 
+    @XmlElementWrapper(name = "jwtSSOProviders")
+    @XmlElement(name = "jwtSSOProvider")
+    @JsonProperty("jwtSSOProviders")
+    public Set<String> getJwtSSOProviders() {
+        return jwtSSOProviders;
+    }
+
     @XmlElementWrapper(name = "reportletConfs")
     @XmlElement(name = "reportletConf")
     @JsonProperty("reportletConfs")

http://git-wip-us.apache.org/repos/asf/syncope/blob/5a7010bb/core/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java
index 149ddea..22cec77 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java
@@ -186,6 +186,7 @@ public class SyncopeLogic extends AbstractLogic<AbstractBaseBean> {
                 PLATFORM_INFO.setPasswordGenerator(AopUtils.getTargetClass(passwordGenerator).getName());
                 PLATFORM_INFO.setAnySearchDAO(AopUtils.getTargetClass(anySearchDAO).getName());
 
+                PLATFORM_INFO.getJwtSSOProviders().addAll(implLookup.getClassNames(Type.JWT_SSO_PROVIDER));
                 PLATFORM_INFO.getReportletConfs().addAll(implLookup.getClassNames(Type.REPORTLET_CONF));
                 PLATFORM_INFO.getAccountRules().addAll(implLookup.getClassNames(Type.ACCOUNT_RULE_CONF));
                 PLATFORM_INFO.getPasswordRules().addAll(implLookup.getClassNames(Type.PASSWORD_RULE_CONF));

http://git-wip-us.apache.org/repos/asf/syncope/blob/5a7010bb/core/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java b/core/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
index 3799150..1fa0043 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
@@ -51,6 +51,7 @@ import org.apache.syncope.core.provisioning.java.job.GroupMemberProvisionTaskJob
 import org.apache.syncope.core.provisioning.java.pushpull.PlainAttrsPullCorrelationRule;
 import org.apache.syncope.core.provisioning.java.pushpull.PullJobDelegate;
 import org.apache.syncope.core.provisioning.java.pushpull.PushJobDelegate;
+import org.apache.syncope.core.spring.security.JWTSSOProvider;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.config.BeanDefinition;
@@ -69,6 +70,8 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup {
 
     private Map<Type, Set<String>> classNames;
 
+    private Set<Class<?>> jwtSSOProviderClasses;
+
     private Map<Class<? extends ReportletConf>, Class<? extends Reportlet>> reportletClasses;
 
     private Map<Class<? extends AccountRuleConf>, Class<? extends AccountRule>> accountRuleClasses;
@@ -97,11 +100,13 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup {
             classNames.put(type, new HashSet<String>());
         }
 
+        jwtSSOProviderClasses = new HashSet<>();
         reportletClasses = new HashMap<>();
         accountRuleClasses = new HashMap<>();
         passwordRuleClasses = new HashMap<>();
 
         ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
+        scanner.addIncludeFilter(new AssignableTypeFilter(JWTSSOProvider.class));
         scanner.addIncludeFilter(new AssignableTypeFilter(Reportlet.class));
         scanner.addIncludeFilter(new AssignableTypeFilter(AccountRule.class));
         scanner.addIncludeFilter(new AssignableTypeFilter(PasswordRule.class));
@@ -122,6 +127,11 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup {
                         bd.getBeanClassName(), ClassUtils.getDefaultClassLoader());
                 boolean isAbstractClazz = Modifier.isAbstract(clazz.getModifiers());
 
+                if (JWTSSOProvider.class.isAssignableFrom(clazz) && !isAbstractClazz) {
+                    classNames.get(Type.JWT_SSO_PROVIDER).add(clazz.getName());
+                    jwtSSOProviderClasses.add(clazz);
+                }
+
                 if (Reportlet.class.isAssignableFrom(clazz) && !isAbstractClazz) {
                     ReportletConfClass annotation = clazz.getAnnotation(ReportletConfClass.class);
                     if (annotation == null) {
@@ -216,6 +226,11 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup {
     }
 
     @Override
+    public Set<Class<?>> getJWTSSOProviderClasses() {
+        return jwtSSOProviderClasses;
+    }
+
+    @Override
     public Class<? extends Reportlet> getReportletClass(
             final Class<? extends ReportletConf> reportletConfClass) {
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/5a7010bb/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/ImplementationLookup.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/ImplementationLookup.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/ImplementationLookup.java
index 61eb327..2d3438f 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/ImplementationLookup.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/ImplementationLookup.java
@@ -30,6 +30,7 @@ public interface ImplementationLookup extends SyncopeLoader {
 
     enum Type {
 
+        JWT_SSO_PROVIDER,
         REPORTLET_CONF,
         ACCOUNT_RULE_CONF,
         PASSWORD_RULE_CONF,
@@ -48,6 +49,8 @@ public interface ImplementationLookup extends SyncopeLoader {
 
     Set<String> getClassNames(Type type);
 
+    Set<Class<?>> getJWTSSOProviderClasses();
+
     Class<? extends Reportlet> getReportletClass(Class<? extends ReportletConf> reportletConfClass);
 
     Class<? extends AccountRule> getAccountRuleClass(Class<? extends AccountRuleConf> accountRuleConfClass);

http://git-wip-us.apache.org/repos/asf/syncope/blob/5a7010bb/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/DummyImplementationLookup.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/DummyImplementationLookup.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/DummyImplementationLookup.java
index dd761b4..4a785ff 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/DummyImplementationLookup.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/DummyImplementationLookup.java
@@ -50,6 +50,11 @@ public class DummyImplementationLookup implements ImplementationLookup {
     }
 
     @Override
+    public Set<Class<?>> getJWTSSOProviderClasses() {
+        return Collections.emptySet();
+    }
+
+    @Override
     public Class<Reportlet> getReportletClass(
             final Class<? extends ReportletConf> reportletConfClass) {
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/5a7010bb/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DummyImplementationLookup.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DummyImplementationLookup.java b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DummyImplementationLookup.java
index 06715d8..011364c 100644
--- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DummyImplementationLookup.java
+++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DummyImplementationLookup.java
@@ -50,6 +50,11 @@ public class DummyImplementationLookup implements ImplementationLookup {
     }
 
     @Override
+    public Set<Class<?>> getJWTSSOProviderClasses() {
+        return Collections.emptySet();
+    }
+
+    @Override
     public Class<Reportlet> getReportletClass(
             final Class<? extends ReportletConf> reportletConfClass) {
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/5a7010bb/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
index c44ad36..d0e69f8 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
@@ -39,6 +39,7 @@ import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.AuditElements;
 import org.apache.syncope.common.lib.types.CipherAlgorithm;
 import org.apache.syncope.common.lib.types.StandardEntitlement;
+import org.apache.syncope.core.persistence.api.ImplementationLookup;
 import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
 import org.apache.syncope.core.provisioning.api.utils.RealmUtils;
@@ -64,10 +65,13 @@ import org.apache.syncope.core.provisioning.api.EntitlementsHolder;
 import org.apache.syncope.core.provisioning.api.MappingManager;
 import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
 import org.apache.syncope.core.provisioning.api.utils.EntityUtils;
+import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.identityconnectors.framework.common.objects.Uid;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
 import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
 import org.springframework.security.authentication.AuthenticationServiceException;
 import org.springframework.security.authentication.DisabledException;
@@ -82,7 +86,7 @@ import org.springframework.transaction.annotation.Transactional;
  * @see UsernamePasswordAuthenticationProvider
  * @see SyncopeAuthenticationDetails
  */
-public class AuthDataAccessor {
+public class AuthDataAccessor implements InitializingBean {
 
     protected static final Logger LOG = LoggerFactory.getLogger(AuthDataAccessor.class);
 
@@ -134,6 +138,30 @@ public class AuthDataAccessor {
     @Autowired
     protected MappingManager mappingManager;
 
+    @Autowired
+    protected ImplementationLookup implementationLookup;
+
+    protected Map<String, JWTSSOProvider> jwtSSOProviders = new HashMap<>();
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        for (Class<?> clazz : implementationLookup.getJWTSSOProviderClasses()) {
+            JWTSSOProvider jwtSSOProvider = (JWTSSOProvider) ApplicationContextProvider.getBeanFactory().
+                    createBean(clazz, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, true);
+            jwtSSOProviders.put(jwtSSOProvider.getIssuer(), jwtSSOProvider);
+        }
+    }
+
+    public JWTSSOProvider getJWTSSOProvider(final String issuer) {
+        JWTSSOProvider provider = jwtSSOProviders.get(issuer);
+        if (provider == null) {
+            throw new AuthenticationCredentialsNotFoundException(
+                    "Could not find any registered JWTSSOProvider for issuer " + issuer);
+        }
+
+        return provider;
+    }
+
     @Transactional(readOnly = true)
     public Domain findDomain(final String key) {
         Domain domain = domainDAO.find(key);
@@ -368,12 +396,17 @@ public class AuthDataAccessor {
         if (adminUser.equals(accessToken.getOwner())) {
             authorities = getAdminAuthorities();
         } else {
-            User user = userDAO.findByUsername(accessToken.getOwner());
+            JWTSSOProvider jwtSSOProvider = getJWTSSOProvider(authentication.getClaims().getIssuer());
+            User user = jwtSSOProvider.resolve(accessToken.getOwner());
             if (user == null) {
                 throw new AuthenticationCredentialsNotFoundException(
                         "Could not find user " + accessToken.getOwner()
                         + " for JWT " + authentication.getClaims().getTokenId());
             }
+            LOG.debug("JWT {} issued by {} resolved to user {}",
+                    authentication.getClaims().getTokenId(),
+                    authentication.getClaims().getIssuer(),
+                    user.getUsername());
 
             if (BooleanUtils.isTrue(user.isSuspended())) {
                 throw new DisabledException("User " + user.getUsername() + " is suspended");

http://git-wip-us.apache.org/repos/asf/syncope/blob/5a7010bb/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultCredentialChecker.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultCredentialChecker.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultCredentialChecker.java
index 3dc0ea0..a75b39e 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultCredentialChecker.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultCredentialChecker.java
@@ -22,15 +22,18 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * This class provides some methods to check whether default credentials are being used, and
- * logs a warning if they are.
+ * Provides some methods to check whether default credentials are being used, and logs a warning if they are.
  */
 public class DefaultCredentialChecker {
+
     private static final Logger LOG = LoggerFactory.getLogger(DefaultCredentialChecker.class);
 
     private static final String DEFAULT_JWS_KEY = "ZW7pRixehFuNUtnY5Se47IemgMryTzazPPJ9CGX5LTCmsOJpOgHAQEuPQeV9A28f";
+
     private static final String DEFAULT_ADMIN_PASSWORD = "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8";
+
     private final boolean defaultAdminPasswordInUse;
+
     private final boolean defaultJwsKeyInUse;
 
     public DefaultCredentialChecker(final String jwsKey, final String adminPassword) {
@@ -41,14 +44,14 @@ public class DefaultCredentialChecker {
     public void checkIsDefaultJWSKeyInUse() {
         if (defaultJwsKeyInUse) {
             LOG.warn("The default jwsKey property is being used. "
-                     + "This must be changed to avoid a security breach!");
+                    + "This must be changed to avoid a security breach!");
         }
     }
 
     public void checkIsDefaultAdminPasswordInUse() {
         if (defaultAdminPasswordInUse) {
             LOG.warn("The default adminPassword property is being used. "
-                     + "This must be changed to avoid a security breach!");
+                    + "This must be changed to avoid a security breach!");
         }
     }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/5a7010bb/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationFilter.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationFilter.java
index e5b13de..faf0576 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationFilter.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationFilter.java
@@ -26,7 +26,6 @@ import javax.servlet.http.HttpServletResponse;
 import javax.ws.rs.core.HttpHeaders;
 
 import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactConsumer;
-import org.apache.cxf.rs.security.jose.jws.JwsSignatureVerifier;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -54,7 +53,7 @@ public class JWTAuthenticationFilter extends OncePerRequestFilter {
     private SyncopeAuthenticationDetailsSource authenticationDetailsSource;
 
     @Autowired
-    private JwsSignatureVerifier jwsSignatureVerifier;
+    private AuthDataAccessor dataAccessor;
 
     @Autowired
     private DefaultCredentialChecker credentialChecker;
@@ -99,7 +98,8 @@ public class JWTAuthenticationFilter extends OncePerRequestFilter {
         try {
             credentialChecker.checkIsDefaultJWSKeyInUse();
 
-            if (!consumer.verifySignatureWith(jwsSignatureVerifier)) {
+            JWTSSOProvider jwtSSOProvider = dataAccessor.getJWTSSOProvider(consumer.getJwtClaims().getIssuer());
+            if (!consumer.verifySignatureWith(jwtSSOProvider)) {
                 throw new BadCredentialsException("Invalid signature found in JWT");
             }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/5a7010bb/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationProvider.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationProvider.java
index 1ff6f0a..b5a3353 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationProvider.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationProvider.java
@@ -19,11 +19,9 @@
 package org.apache.syncope.core.spring.security;
 
 import java.util.Date;
-import javax.annotation.Resource;
 import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.AuthenticationProvider;
-import org.springframework.security.authentication.BadCredentialsException;
 import org.springframework.security.authentication.CredentialsExpiredException;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
@@ -34,9 +32,6 @@ import org.springframework.security.core.AuthenticationException;
  */
 public class JWTAuthenticationProvider implements AuthenticationProvider {
 
-    @Resource(name = "jwtIssuer")
-    private String jwtIssuer;
-
     @Autowired
     private AuthDataAccessor dataAccessor;
 
@@ -45,8 +40,7 @@ public class JWTAuthenticationProvider implements AuthenticationProvider {
         final JWTAuthentication jwtAuthentication = (JWTAuthentication) authentication;
 
         AuthContextUtils.execWithAuthContext(
-                jwtAuthentication.getDetails().getDomain(),
-                new AuthContextUtils.Executable<Void>() {
+                jwtAuthentication.getDetails().getDomain(), new AuthContextUtils.Executable<Void>() {
 
             @Override
             public Void exec() {
@@ -69,10 +63,6 @@ public class JWTAuthenticationProvider implements AuthenticationProvider {
             throw new CredentialsExpiredException("JWT not valid yet");
         }
 
-        if (!jwtIssuer.equals(claims.getIssuer())) {
-            throw new BadCredentialsException("Invalid JWT issuer");
-        }
-
         jwtAuthentication.setAuthenticated(true);
         return jwtAuthentication;
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/5a7010bb/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTSSOProvider.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTSSOProvider.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTSSOProvider.java
new file mode 100644
index 0000000..63e7087
--- /dev/null
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTSSOProvider.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.spring.security;
+
+import org.apache.cxf.rs.security.jose.jws.JwsSignatureVerifier;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+
+/**
+ * Enables a generic mechanism for JWT validation and subject resolution which allows to plug in implementations
+ * recognizing JWT produced by third parties.
+ */
+public interface JWTSSOProvider extends JwsSignatureVerifier {
+
+    /**
+     * Gives the identifier for the JWT issuer verified by this instance.
+     *
+     * @return identifier for the JWT issuer verified by this instance
+     */
+    String getIssuer();
+
+    /**
+     * Attempts to resolve the subject from a given JWT into an internal user.
+     *
+     * @param jwtSubject subject from JWT claims
+     * @return internal user matching the provided subject if found, otherwise null
+     */
+    User resolve(String jwtSubject);
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/5a7010bb/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeJWTSSOProvider.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..4cd1a9f
--- /dev/null
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeJWTSSOProvider.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.spring.security;
+
+import javax.annotation.Resource;
+import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm;
+import org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureVerifier;
+import org.apache.cxf.rs.security.jose.jws.JwsHeaders;
+import org.apache.cxf.rs.security.jose.jws.JwsSignatureProvider;
+import org.apache.cxf.rs.security.jose.jws.JwsSignatureVerifier;
+import org.apache.cxf.rs.security.jose.jws.JwsVerificationSignature;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Default implementation for internal JWT validation.
+ */
+public class SyncopeJWTSSOProvider implements JWTSSOProvider, InitializingBean {
+
+    @Resource(name = "jwtIssuer")
+    private String jwtIssuer;
+
+    @Resource(name = "jwsKey")
+    private String jwsKey;
+
+    @Autowired
+    private JwsSignatureProvider signatureProvider;
+
+    @Autowired
+    private UserDAO userDAO;
+
+    private JwsSignatureVerifier delegate;
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        delegate = new HmacJwsSignatureVerifier(jwsKey.getBytes(), signatureProvider.getAlgorithm());
+    }
+
+    @Override
+    public String getIssuer() {
+        return jwtIssuer;
+    }
+
+    @Override
+    public SignatureAlgorithm getAlgorithm() {
+        return delegate.getAlgorithm();
+    }
+
+    @Override
+    public boolean verify(final JwsHeaders headers, final String unsignedText, final byte[] signature) {
+        return delegate.verify(headers, unsignedText, signature);
+    }
+
+    @Override
+    public JwsVerificationSignature createJwsVerificationSignature(final JwsHeaders headers) {
+        return delegate.createJwsVerificationSignature(headers);
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public User resolve(final String jwtSubject) {
+        return userDAO.findByUsername(jwtSubject);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/5a7010bb/core/spring/src/main/resources/securityContext.xml
----------------------------------------------------------------------
diff --git a/core/spring/src/main/resources/securityContext.xml b/core/spring/src/main/resources/securityContext.xml
index c9016fa..cc1445c 100644
--- a/core/spring/src/main/resources/securityContext.xml
+++ b/core/spring/src/main/resources/securityContext.xml
@@ -50,16 +50,10 @@ under the License.
   </bean>
 
   <bean id="credentialChecker" class="org.apache.syncope.core.spring.security.DefaultCredentialChecker">
-      <constructor-arg value="${jwsKey}" index="0"/>
-      <constructor-arg value="${adminPassword}" index="1"/>
+    <constructor-arg value="${jwsKey}" index="0"/>
+    <constructor-arg value="${adminPassword}" index="1"/>
   </bean>
 
-  <bean id="jwsSignatureVerifier" class="org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureVerifier">
-    <constructor-arg value="#{jwsKey.getBytes()}" index="0"/>
-    <constructor-arg index="1">
-      <value type="org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm">HS512</value>
-    </constructor-arg>
-  </bean>
   <bean id="jwsSignatureProvider" class="org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureProvider">
     <constructor-arg value="#{jwsKey.getBytes()}" index="0"/>
     <constructor-arg index="1">

http://git-wip-us.apache.org/repos/asf/syncope/blob/5a7010bb/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2SPLogic.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2SPLogic.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2SPLogic.java
index e5c444c..1ea3601 100644
--- a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2SPLogic.java
+++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2SPLogic.java
@@ -31,11 +31,14 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import javax.annotation.Resource;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.commons.lang3.tuple.Triple;
+import org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureVerifier;
 import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactConsumer;
+import org.apache.cxf.rs.security.jose.jws.JwsSignatureProvider;
 import org.apache.cxf.rs.security.jose.jws.JwsSignatureVerifier;
 import org.apache.syncope.common.lib.AbstractBaseBean;
 import org.apache.syncope.common.lib.SyncopeClientException;
@@ -114,13 +117,14 @@ import org.opensaml.saml.saml2.metadata.impl.SPSSODescriptorBuilder;
 import org.opensaml.saml.saml2.metadata.impl.SingleLogoutServiceBuilder;
 import org.opensaml.xmlsec.keyinfo.KeyInfoGenerator;
 import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory;
+import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.security.authentication.BadCredentialsException;
 import org.springframework.stereotype.Component;
 
 @Component
-public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> {
+public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> implements InitializingBean {
 
     private static final Integer JWT_RELAY_STATE_DURATION = 5;
 
@@ -137,9 +141,6 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> {
     private static final RandomBasedGenerator UUID_GENERATOR = Generators.randomBasedGenerator();
 
     @Autowired
-    private JwsSignatureVerifier jwsSignatureCerifier;
-
-    @Autowired
     private AccessTokenDataBinder accessTokenDataBinder;
 
     @Autowired
@@ -166,6 +167,19 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> {
     @Autowired
     private SAML2ReaderWriter saml2rw;
 
+    @Resource(name = "jwsKey")
+    private String jwsKey;
+
+    @Autowired
+    private JwsSignatureProvider jwsSignatureProvider;
+
+    private JwsSignatureVerifier jwsSignatureVerifier;
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        jwsSignatureVerifier = new HmacJwsSignatureVerifier(jwsKey.getBytes(), jwsSignatureProvider.getAlgorithm());
+    }
+
     @PreAuthorize("hasRole('" + StandardEntitlement.ANONYMOUS + "')")
     public void getMetadata(final String spEntityID, final String urlContext, final OutputStream os) {
         check();
@@ -412,7 +426,7 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> {
 
         // 1. first checks for the provided relay state
         JwsJwtCompactConsumer relayState = new JwsJwtCompactConsumer(response.getRelayState());
-        if (!relayState.verifySignatureWith(jwsSignatureCerifier)) {
+        if (!relayState.verifySignatureWith(jwsSignatureVerifier)) {
             throw new IllegalArgumentException("Invalid signature found in Relay State");
         }
         Boolean useDeflateEncoding = Boolean.valueOf(
@@ -544,7 +558,7 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> {
 
         // 1. fetch the current JWT used for Syncope authentication
         JwsJwtCompactConsumer consumer = new JwsJwtCompactConsumer(accessToken);
-        if (!consumer.verifySignatureWith(jwsSignatureCerifier)) {
+        if (!consumer.verifySignatureWith(jwsSignatureVerifier)) {
             throw new IllegalArgumentException("Invalid signature found in Access Token");
         }
 
@@ -624,7 +638,7 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> {
 
         // 1. fetch the current JWT used for Syncope authentication
         JwsJwtCompactConsumer consumer = new JwsJwtCompactConsumer(accessToken);
-        if (!consumer.verifySignatureWith(jwsSignatureCerifier)) {
+        if (!consumer.verifySignatureWith(jwsSignatureVerifier)) {
             throw new IllegalArgumentException("Invalid signature found in Access Token");
         }
 
@@ -634,7 +648,7 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> {
         if (StringUtils.isNotBlank(response.getRelayState())) {
             // first checks for the provided relay state, if available
             relayState = new JwsJwtCompactConsumer(response.getRelayState());
-            if (!relayState.verifySignatureWith(jwsSignatureCerifier)) {
+            if (!relayState.verifySignatureWith(jwsSignatureVerifier)) {
                 throw new IllegalArgumentException("Invalid signature found in Relay State");
             }
             useDeflateEncoding = Boolean.valueOf(

http://git-wip-us.apache.org/repos/asf/syncope/blob/5a7010bb/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
index ea86696..4e76d49 100644
--- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.fit.core.reference;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -61,6 +62,7 @@ import org.apache.syncope.core.provisioning.java.pushpull.DBPasswordPullActions;
 import org.apache.syncope.core.provisioning.java.pushpull.LDAPMembershipPullActions;
 import org.apache.syncope.core.provisioning.java.pushpull.LDAPPasswordPullActions;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.apache.syncope.core.spring.security.SyncopeJWTSSOProvider;
 import org.springframework.aop.support.AopUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -223,6 +225,11 @@ public class ITImplementationLookup implements ImplementationLookup {
     }
 
     @Override
+    public Set<Class<?>> getJWTSSOProviderClasses() {
+        return Collections.<Class<?>>singleton(SyncopeJWTSSOProvider.class);
+    }
+
+    @Override
     public Class<? extends Reportlet> getReportletClass(
             final Class<? extends ReportletConf> reportletConfClass) {
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/5a7010bb/fit/core-reference/src/test/java/org/apache/syncope/fit/core/JWTITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/JWTITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/JWTITCase.java
index 27af849..e01bdf4 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/JWTITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/JWTITCase.java
@@ -51,12 +51,12 @@ import org.apache.syncope.fit.AbstractITCase;
 import org.junit.Test;
 
 /**
- * Some tests for JWT Tokens
+ * Some tests for JWT Tokens.
  */
 public class JWTITCase extends AbstractITCase {
 
     @Test
-    public void testGetJWTToken() throws ParseException {
+    public void getJWTToken() throws ParseException {
         // Get the token
         SyncopeClient localClient = clientFactory.create(ADMIN_UNAME, ADMIN_PWD);
         AccessTokenService accessTokenService = localClient.getService(AccessTokenService.class);
@@ -92,7 +92,7 @@ public class JWTITCase extends AbstractITCase {
         assertTrue(new Date(issuedAt).before(now));
 
         // Validate subject + issuer
-        assertEquals("admin", consumer.getJwtClaims().getSubject());
+        assertEquals(ADMIN_UNAME, consumer.getJwtClaims().getSubject());
         assertEquals(JWT_ISSUER, consumer.getJwtClaims().getIssuer());
 
         // Verify NotBefore
@@ -102,7 +102,7 @@ public class JWTITCase extends AbstractITCase {
     }
 
     @Test
-    public void testQueryUsingToken() throws ParseException {
+    public void queryUsingToken() throws ParseException {
         // Get the token
         SyncopeClient localClient = clientFactory.create(ADMIN_UNAME, ADMIN_PWD);
         AccessTokenService accessTokenService = localClient.getService(AccessTokenService.class);
@@ -128,7 +128,7 @@ public class JWTITCase extends AbstractITCase {
     }
 
     @Test
-    public void testTokenValidation() throws ParseException {
+    public void tokenValidation() throws ParseException {
         // Get an initial token
         SyncopeClient localClient = clientFactory.create(ADMIN_UNAME, ADMIN_PWD);
         AccessTokenService accessTokenService = localClient.getService(AccessTokenService.class);
@@ -148,7 +148,7 @@ public class JWTITCase extends AbstractITCase {
 
         JwtClaims jwtClaims = new JwtClaims();
         jwtClaims.setTokenId(tokenId);
-        jwtClaims.setSubject("admin");
+        jwtClaims.setSubject(ADMIN_UNAME);
         jwtClaims.setIssuedAt(now.getTime());
         jwtClaims.setIssuer(JWT_ISSUER);
         jwtClaims.setExpiryTime(expiry.getTime().getTime());
@@ -168,7 +168,7 @@ public class JWTITCase extends AbstractITCase {
     }
 
     @Test
-    public void testInvalidIssuer() throws ParseException {
+    public void invalidIssuer() throws ParseException {
         // Get an initial token
         SyncopeClient localClient = clientFactory.create(ADMIN_UNAME, ADMIN_PWD);
         AccessTokenService accessTokenService = localClient.getService(AccessTokenService.class);
@@ -188,7 +188,7 @@ public class JWTITCase extends AbstractITCase {
 
         JwtClaims jwtClaims = new JwtClaims();
         jwtClaims.setTokenId(tokenId);
-        jwtClaims.setSubject("admin");
+        jwtClaims.setSubject(ADMIN_UNAME);
         jwtClaims.setIssuedAt(now.getTime());
         jwtClaims.setIssuer("UnknownIssuer");
         jwtClaims.setExpiryTime(expiry.getTime().getTime());
@@ -213,7 +213,7 @@ public class JWTITCase extends AbstractITCase {
     }
 
     @Test
-    public void testExpiredToken() throws ParseException {
+    public void expiredToken() throws ParseException {
         // Get an initial token
         SyncopeClient localClient = clientFactory.create(ADMIN_UNAME, ADMIN_PWD);
         AccessTokenService accessTokenService = localClient.getService(AccessTokenService.class);
@@ -233,7 +233,7 @@ public class JWTITCase extends AbstractITCase {
 
         JwtClaims jwtClaims = new JwtClaims();
         jwtClaims.setTokenId(tokenId);
-        jwtClaims.setSubject("admin");
+        jwtClaims.setSubject(ADMIN_UNAME);
         jwtClaims.setIssuedAt(now.getTime());
         jwtClaims.setIssuer(JWT_ISSUER);
         jwtClaims.setExpiryTime(now.getTime() - 5000L);
@@ -258,7 +258,7 @@ public class JWTITCase extends AbstractITCase {
     }
 
     @Test
-    public void testNotBefore() throws ParseException {
+    public void notBefore() throws ParseException {
         // Get an initial token
         SyncopeClient localClient = clientFactory.create(ADMIN_UNAME, ADMIN_PWD);
         AccessTokenService accessTokenService = localClient.getService(AccessTokenService.class);
@@ -278,7 +278,7 @@ public class JWTITCase extends AbstractITCase {
 
         JwtClaims jwtClaims = new JwtClaims();
         jwtClaims.setTokenId(tokenId);
-        jwtClaims.setSubject("admin");
+        jwtClaims.setSubject(ADMIN_UNAME);
         jwtClaims.setIssuedAt(now.getTime());
         jwtClaims.setIssuer(JWT_ISSUER);
         jwtClaims.setExpiryTime(expiry.getTime().getTime());
@@ -303,7 +303,7 @@ public class JWTITCase extends AbstractITCase {
     }
 
     @Test
-    public void testNoneSignature() throws ParseException {
+    public void noneSignature() throws ParseException {
         // Get an initial token
         SyncopeClient localClient = clientFactory.create(ADMIN_UNAME, ADMIN_PWD);
         AccessTokenService accessTokenService = localClient.getService(AccessTokenService.class);
@@ -341,7 +341,7 @@ public class JWTITCase extends AbstractITCase {
     }
 
     @Test
-    public void testUnknownId() throws ParseException {
+    public void unknownId() throws ParseException {
         // Get an initial token
         SyncopeClient localClient = clientFactory.create(ADMIN_UNAME, ADMIN_PWD);
         AccessTokenService accessTokenService = localClient.getService(AccessTokenService.class);
@@ -359,7 +359,7 @@ public class JWTITCase extends AbstractITCase {
 
         JwtClaims jwtClaims = new JwtClaims();
         jwtClaims.setTokenId(UUID.randomUUID().toString());
-        jwtClaims.setSubject("admin");
+        jwtClaims.setSubject(ADMIN_UNAME);
         jwtClaims.setIssuedAt(now.getTime());
         jwtClaims.setIssuer(JWT_ISSUER);
         jwtClaims.setExpiryTime(expiry.getTime().getTime());

http://git-wip-us.apache.org/repos/asf/syncope/blob/5a7010bb/src/main/asciidoc/reference-guide/workingwithapachesyncope/customization.adoc
----------------------------------------------------------------------
diff --git a/src/main/asciidoc/reference-guide/workingwithapachesyncope/customization.adoc b/src/main/asciidoc/reference-guide/workingwithapachesyncope/customization.adoc
index e0c09a5..f73e360 100644
--- a/src/main/asciidoc/reference-guide/workingwithapachesyncope/customization.adoc
+++ b/src/main/asciidoc/reference-guide/workingwithapachesyncope/customization.adoc
@@ -192,6 +192,7 @@ Besides replacing existing classes as explained <<override-behavior,above>>, new
 * <<workflow-adapters,workflow adapters>>
 * <<provisioning-managers,provisioning managers>>
 * <<notifications,notification recipient providers>>
+* <<jwtssoprovider,JWT SSO providers>>
 
 [[new-rest-endpoints]]
 [TIP]

http://git-wip-us.apache.org/repos/asf/syncope/blob/5a7010bb/src/main/asciidoc/reference-guide/workingwithapachesyncope/restfulservices.adoc
----------------------------------------------------------------------
diff --git a/src/main/asciidoc/reference-guide/workingwithapachesyncope/restfulservices.adoc b/src/main/asciidoc/reference-guide/workingwithapachesyncope/restfulservices.adoc
index da00883..0ec7eaf 100644
--- a/src/main/asciidoc/reference-guide/workingwithapachesyncope/restfulservices.adoc
+++ b/src/main/asciidoc/reference-guide/workingwithapachesyncope/restfulservices.adoc
@@ -72,6 +72,18 @@ while normally not needed, this configuration can be anyway customized via the <
 https://en.wikipedia.org/wiki/Basic_access_authentication[HTTP Basic Authentication] is set for use by default.
 ====
 
+===== JWTSSOProvider
+
+Besides validating and accepting the JSON Web Tokens generated during the authentication process as sketched above,
+Apache Syncope can be enabled to cope with tokens generated by third parties, by providing implementations of the
+ifeval::["{snapshotOrRelease}" == "release"]
+https://github.com/apache/syncope/blob/syncope-{docVersion}/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTSSOProvider.java[JWTSSOProvider^]
+endif::[]
+ifeval::["{snapshotOrRelease}" == "snapshot"]
+https://github.com/apache/syncope/tree/2_0_X/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTSSOProvider.java[JWTSSOProvider^]
+endif::[]
+interface.
+
 [[authorization-summary]]
 .Authorization Summary
 ****