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:26 UTC
[1/2] syncope git commit: [SYNCOPE-1129] New interface JWTSSOProvider
with default impl SyncopeJWTSSOProvider + docs
Repository: syncope
Updated Branches:
refs/heads/2_0_X f6e9fa3a5 -> 5a7010bb3
refs/heads/master 653afac68 -> 94708a3ee
[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/94708a3e
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/94708a3e
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/94708a3e
Branch: refs/heads/master
Commit: 94708a3eed564f6bc33953075d7ac423a4ec167d
Parents: 653afac
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:04 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/94708a3e/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 bbe843d..4a71624 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
@@ -63,6 +63,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<>();
@@ -165,6 +167,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/94708a3e/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 82ca4b9..2c3d148 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
@@ -183,6 +183,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/94708a3e/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/94708a3e/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/94708a3e/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/94708a3e/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/94708a3e/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/94708a3e/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/94708a3e/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/94708a3e/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/94708a3e/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/94708a3e/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/94708a3e/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/94708a3e/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/94708a3e/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/94708a3e/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/94708a3e/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 6339b66..52449e2 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/94708a3e/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 8627087..ea317f9 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/master/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTSSOProvider.java[JWTSSOProvider^]
+endif::[]
+interface.
+
[[authorization-summary]]
.Authorization Summary
****
[2/2] syncope git commit: [SYNCOPE-1129] New interface JWTSSOProvider
with default impl SyncopeJWTSSOProvider + docs
Posted by il...@apache.org.
[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
****