syncope-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ilgro...@apache.org
Subject [3/8] syncope git commit: [SYNCOPE-1035] Using JWT as authentication mean, obtained via initial call
Date Thu, 02 Mar 2017 10:58:36 GMT
[SYNCOPE-1035] Using JWT as authentication mean, obtained via initial call


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

Branch: refs/heads/2_0_X
Commit: 521f51a9dd2face373ed7437837a8de82a609675
Parents: 077edf8
Author: Francesco Chicchiriccò <ilgrosso@apache.org>
Authored: Tue Feb 28 17:58:39 2017 +0100
Committer: Francesco Chicchiriccò <ilgrosso@apache.org>
Committed: Thu Mar 2 11:52:44 2017 +0100

----------------------------------------------------------------------
 .../syncope/client/cli/SyncopeServices.java     |   2 +-
 .../client/console/SyncopeConsoleSession.java   |  45 ++--
 .../client/console/rest/BaseRestClient.java     |  11 +-
 .../console/rest/ConnectorRestClient.java       |   4 +-
 .../client/console/rest/ResourceRestClient.java |   4 +-
 .../client/enduser/SyncopeEnduserSession.java   |  48 ++--
 .../lib/AnonymousAuthenticationHandler.java     |  30 +++
 .../client/lib/AuthenticationHandler.java       |  27 +++
 .../client/lib/BasicAuthenticationHandler.java  |  43 ++++
 .../client/lib/JWTAuthenticationHandler.java    |  36 +++
 .../client/lib/NoAuthenticationHandler.java     |  26 ++
 .../client/lib/RestClientFactoryBean.java       |  91 -------
 .../syncope/client/lib/SyncopeClient.java       | 192 ++++++++++-----
 .../client/lib/SyncopeClientFactoryBean.java    |  45 +++-
 .../syncope/client/lib/ConcurrencyTest.java     |  13 +-
 .../syncope/common/lib/SyncopeConstants.java    |   2 +
 .../syncope/common/lib/to/AccessTokenTO.java    |  88 +++++++
 .../common/lib/types/StandardEntitlement.java   |   4 +
 .../syncope/common/rest/api/RESTHeaders.java    |   2 +
 .../common/rest/api/beans/AccessTokenQuery.java |  33 +++
 .../rest/api/service/AccessTokenService.java    |  82 +++++++
 core/logic/pom.xml                              |  10 +
 .../syncope/core/logic/AccessTokenLogic.java    | 187 +++++++++++++++
 .../apache/syncope/core/logic/ReportLogic.java  |   3 +-
 .../persistence/api/dao/AccessTokenDAO.java     |  42 ++++
 .../persistence/api/entity/AccessToken.java     |  36 +++
 .../persistence/jpa/dao/JPAAccessTokenDAO.java  | 143 +++++++++++
 .../core/persistence/jpa/dao/JPAUserDAO.java    |  10 +
 .../persistence/jpa/entity/JPAAccessToken.java  |  83 +++++++
 .../jpa/entity/JPAEntityFactory.java            |   3 +
 .../main/resources/domains/MasterContent.xml    |  13 +
 .../persistence/jpa/inner/MultitenancyTest.java |   2 +-
 .../persistence/jpa/inner/PlainSchemaTest.java  |   2 +-
 .../core/persistence/jpa/inner/TaskTest.java    |   2 +-
 .../persistence/jpa/outer/AccessTokenTest.java  |  64 +++++
 .../test/resources/domains/MasterContent.xml    |  12 +
 .../src/test/resources/domains/TwoContent.xml   |  18 ++
 .../api/data/AccessTokenDataBinder.java         |  33 +++
 .../api/serialization/POJOHelper.java           |  13 +
 .../java/data/AccessTokenDataBinderImpl.java    |  70 ++++++
 .../java/data/UserDataBinderImpl.java           |  11 +
 .../java/job/ExpiredAccessTokenCleanup.java     |  42 ++++
 .../java/job/IdentityRecertification.java       |  21 +-
 .../cxf/service/AccessTokenServiceImpl.java     |  73 ++++++
 core/spring/pom.xml                             |   5 +
 .../core/spring/security/AuthDataAccessor.java  | 106 +++++++--
 .../core/spring/security/JWTAuthentication.java |  88 +++++++
 .../security/JWTAuthenticationFilter.java       | 108 +++++++++
 .../security/JWTAuthenticationProvider.java     |  91 +++++++
 .../security/SyncopeAuthenticationDetails.java  |  34 +--
 .../security/SyncopeAuthenticationProvider.java | 235 -------------------
 .../SyncopeDigestAuthenticationEntryPoint.java  |  43 ++++
 .../security/SyncopeGrantedAuthority.java       |   8 +-
 .../UsernamePasswordAuthenticationProvider.java | 210 +++++++++++++++++
 .../src/main/resources/security.properties      |   4 +
 .../src/main/resources/securityContext.xml      |  90 ++++---
 .../org/apache/syncope/fit/AbstractITCase.java  |   6 +-
 .../syncope/fit/core/AuthenticationITCase.java  |  70 ++++--
 .../apache/syncope/fit/core/GroupITCase.java    |   5 +-
 .../syncope/fit/core/MultitenancyITCase.java    |   2 +-
 .../org/apache/syncope/fit/core/RESTITCase.java |  12 +-
 .../apache/syncope/fit/core/ResourceITCase.java |   4 +-
 .../org/apache/syncope/fit/core/UserITCase.java |   9 +-
 .../syncope/fit/core/VirSchemaITCase.java       |   5 +-
 pom.xml                                         |  21 +-
 .../concepts/typemanagement.adoc                |   2 +-
 66 files changed, 2296 insertions(+), 583 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/client/cli/src/main/java/org/apache/syncope/client/cli/SyncopeServices.java
----------------------------------------------------------------------
diff --git a/client/cli/src/main/java/org/apache/syncope/client/cli/SyncopeServices.java b/client/cli/src/main/java/org/apache/syncope/client/cli/SyncopeServices.java
index d534930..beb5feb 100644
--- a/client/cli/src/main/java/org/apache/syncope/client/cli/SyncopeServices.java
+++ b/client/cli/src/main/java/org/apache/syncope/client/cli/SyncopeServices.java
@@ -53,7 +53,7 @@ public final class SyncopeServices {
                 setUseCompression(BooleanUtils.toBoolean(useGZIPCompression)).
                 create(properties.getProperty("syncope.admin.user"), syncopeAdminPassword);
 
-        LOG.debug("Creting service for {}", clazz.getName());
+        LOG.debug("Creating service for {}", clazz.getName());
         return syncopeClient.getService(clazz);
     }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java b/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
index 658e52b..1ce3b71 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
@@ -37,8 +37,8 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.time.FastDateFormat;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.syncope.client.lib.AnonymousAuthenticationHandler;
 import org.apache.syncope.client.lib.SyncopeClient;
-import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.EntityTOUtils;
 import org.apache.syncope.common.lib.to.DomainTO;
@@ -87,10 +87,6 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession {
 
     private SyncopeClient client;
 
-    private String username;
-
-    private String password;
-
     private UserTO selfTO;
 
     private Map<String, Set<String>> auth;
@@ -106,9 +102,10 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession {
     public SyncopeConsoleSession(final Request request) {
         super(request);
 
-        SyncopeClient anonymousClient = SyncopeConsoleApplication.get().getClientFactory().create(
-                SyncopeConsoleApplication.get().getAnonymousUser(),
-                SyncopeConsoleApplication.get().getAnonymousKey());
+        SyncopeClient anonymousClient = SyncopeConsoleApplication.get().getClientFactory().
+                create(new AnonymousAuthenticationHandler(
+                        SyncopeConsoleApplication.get().getAnonymousUser(),
+                        SyncopeConsoleApplication.get().getAnonymousKey()));
 
         platformInfo = anonymousClient.getService(SyncopeService.class).platform();
         systemInfo = anonymousClient.getService(SyncopeService.class).system();
@@ -134,8 +131,16 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession {
 
     @Override
     public void invalidate() {
+        client.logout();
+        executorService.shutdown();
         super.invalidate();
+    }
+
+    @Override
+    public void invalidateNow() {
+        client.logout();
         executorService.shutdownNow();
+        super.invalidateNow();
     }
 
     public PlatformInfo getPlatformInfo() {
@@ -158,6 +163,10 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession {
         return StringUtils.isBlank(domain) ? SyncopeConstants.MASTER_DOMAIN : domain;
     }
 
+    public String getJWT() {
+        return client.getJWT();
+    }
+
     @Override
     public boolean authenticate(final String username, final String password) {
         boolean authenticated = false;
@@ -170,8 +179,6 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession {
             auth = self.getKey();
             selfTO = self.getValue();
 
-            this.username = username;
-            this.password = password;
             authenticated = true;
         } catch (Exception e) {
             LOG.error("Authentication failed", e);
@@ -199,7 +206,7 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession {
     }
 
     public void refreshAuth() {
-        authenticate(username, password);
+        client.refresh();
         roles = null;
     }
 
@@ -213,8 +220,7 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession {
             services.put(serviceClass, service);
         }
 
-        WebClient.client(service).
-                type(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON);
+        WebClient.client(service).type(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON);
 
         return service;
     }
@@ -231,18 +237,9 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession {
     }
 
     public <T> T getService(final MediaType mediaType, final Class<T> serviceClass) {
-        T service;
+        T service = client.getService(serviceClass);
 
-        synchronized (SyncopeConsoleApplication.get().getClientFactory()) {
-            SyncopeClientFactoryBean.ContentType preType = SyncopeConsoleApplication.get().getClientFactory().
-                    getContentType();
-
-            SyncopeConsoleApplication.get().getClientFactory().
-                    setContentType(SyncopeClientFactoryBean.ContentType.fromString(mediaType.toString()));
-            service = SyncopeConsoleApplication.get().getClientFactory().
-                    create(username, password).getService(serviceClass);
-            SyncopeConsoleApplication.get().getClientFactory().setContentType(preType);
-        }
+        WebClient.client(service).type(mediaType).accept(mediaType);
 
         return service;
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/client/console/src/main/java/org/apache/syncope/client/console/rest/BaseRestClient.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/BaseRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/BaseRestClient.java
index 9eaa65d..d2c73f1 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/rest/BaseRestClient.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/BaseRestClient.java
@@ -24,6 +24,7 @@ import org.apache.syncope.client.console.SyncopeConsoleApplication;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.common.lib.search.OrderByClauseBuilder;
+import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.service.JAXRSService;
 import org.apache.syncope.common.rest.api.service.SyncopeService;
 import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
@@ -69,10 +70,14 @@ public abstract class BaseRestClient implements RestClient {
         return builder.build();
     }
 
-    protected static <E extends JAXRSService, T> T getObject(final E service, final URI location,
-            final Class<T> resultClass) {
+    protected static <E extends JAXRSService, T> T getObject(
+            final E service, final URI location, final Class<T> resultClass) {
+
         WebClient webClient = WebClient.fromClient(WebClient.client(service));
         webClient.accept(SyncopeConsoleApplication.get().getMediaType()).to(location.toASCIIString(), false);
-        return webClient.get(resultClass);
+        return webClient.
+                header(RESTHeaders.DOMAIN, SyncopeConsoleSession.get().getDomain()).
+                header(RESTHeaders.TOKEN, SyncopeConsoleSession.get().getJWT()).
+                get(resultClass);
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/client/console/src/main/java/org/apache/syncope/client/console/rest/ConnectorRestClient.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/ConnectorRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/ConnectorRestClient.java
index e506a77..7ac3fcc 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/rest/ConnectorRestClient.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/ConnectorRestClient.java
@@ -58,8 +58,8 @@ public class ConnectorRestClient extends BaseRestClient {
         connectorTO.getConf().clear();
         connectorTO.getConf().addAll(filteredConf);
 
-        final ConnectorService service = getService(ConnectorService.class);
-        final Response response = service.create(connectorTO);
+        ConnectorService service = getService(ConnectorService.class);
+        Response response = service.create(connectorTO);
 
         return getObject(service, response.getLocation(), ConnInstanceTO.class);
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceRestClient.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceRestClient.java
index 46e7d3e..7ba375d 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceRestClient.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceRestClient.java
@@ -104,8 +104,8 @@ public class ResourceRestClient extends BaseRestClient {
     }
 
     public ResourceTO create(final ResourceTO resourceTO) {
-        final ResourceService service = getService(ResourceService.class);
-        final Response response = service.create(resourceTO);
+        ResourceService service = getService(ResourceService.class);
+        Response response = service.create(resourceTO);
         return getObject(service, response.getLocation(), ResourceTO.class);
     }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java
index cd2fe24..7c1af3b 100644
--- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java
@@ -18,9 +18,7 @@
  */
 package org.apache.syncope.client.enduser;
 
-import java.text.DateFormat;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import javax.ws.rs.core.EntityTag;
@@ -29,6 +27,7 @@ import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.collections4.Predicate;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.syncope.client.lib.AnonymousAuthenticationHandler;
 import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.common.lib.info.PlatformInfo;
 import org.apache.syncope.common.lib.to.PlainSchemaTO;
@@ -58,10 +57,6 @@ public class SyncopeEnduserSession extends WebSession {
 
     private SyncopeClient client;
 
-    private String username;
-
-    private String password;
-
     private final PlatformInfo platformInfo;
 
     private final List<PlainSchemaTO> datePlainSchemas;
@@ -81,9 +76,10 @@ public class SyncopeEnduserSession extends WebSession {
         // define cookie utility to manage application cookies
         cookieUtils = new CookieUtils();
 
-        anonymousClient = SyncopeEnduserApplication.get().getClientFactory().create(
-                SyncopeEnduserApplication.get().getAnonymousUser(),
-                SyncopeEnduserApplication.get().getAnonymousKey());
+        anonymousClient = SyncopeEnduserApplication.get().getClientFactory().
+                create(new AnonymousAuthenticationHandler(
+                        SyncopeEnduserApplication.get().getAnonymousUser(),
+                        SyncopeEnduserApplication.get().getAnonymousKey()));
         platformInfo = anonymousClient.getService(SyncopeService.class).platform();
 
         datePlainSchemas = anonymousClient.getService(SchemaService.class).
@@ -108,10 +104,8 @@ public class SyncopeEnduserSession extends WebSession {
             Pair<Map<String, Set<String>>, UserTO> self = client.self();
             selfTO = self.getValue();
 
-            this.username = username;
-            this.password = password;
-            // bind explicitly this session to have a stateful behavior during http requests, unless session will expire
-            // for every  request
+            // bind explicitly this session to have a stateful behavior during http requests, unless session will
+            // expire for every request
             this.bind();
             authenticated = true;
         } catch (Exception e) {
@@ -135,14 +129,6 @@ public class SyncopeEnduserSession extends WebSession {
         return serviceInstance;
     }
 
-    public String getUsername() {
-        return username;
-    }
-
-    public String getPassword() {
-        return password;
-    }
-
     public PlatformInfo getPlatformInfo() {
         return platformInfo;
     }
@@ -156,13 +142,7 @@ public class SyncopeEnduserSession extends WebSession {
     }
 
     public boolean isAuthenticated() {
-        return getUsername() != null;
-    }
-
-    public DateFormat getDateFormat() {
-        final Locale locale = getLocale() == null ? Locale.ENGLISH : getLocale();
-
-        return DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale);
+        return client.getJWT() != null;
     }
 
     public CookieUtils getCookieUtils() {
@@ -177,4 +157,16 @@ public class SyncopeEnduserSession extends WebSession {
         this.xsrfTokenGenerated = xsrfTokenGenerated;
     }
 
+    @Override
+    public void invalidate() {
+        client.logout();
+        super.invalidate();
+    }
+
+    @Override
+    public void invalidateNow() {
+        client.logout();
+        super.invalidateNow();
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/client/lib/src/main/java/org/apache/syncope/client/lib/AnonymousAuthenticationHandler.java
----------------------------------------------------------------------
diff --git a/client/lib/src/main/java/org/apache/syncope/client/lib/AnonymousAuthenticationHandler.java b/client/lib/src/main/java/org/apache/syncope/client/lib/AnonymousAuthenticationHandler.java
new file mode 100644
index 0000000..b34be66
--- /dev/null
+++ b/client/lib/src/main/java/org/apache/syncope/client/lib/AnonymousAuthenticationHandler.java
@@ -0,0 +1,30 @@
+/*
+ * 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.client.lib;
+
+/**
+ * Implementation providing Basic Authentication capability for the special {@code anonymous} user.
+ */
+public class AnonymousAuthenticationHandler extends BasicAuthenticationHandler implements AuthenticationHandler {
+
+    public AnonymousAuthenticationHandler(final String username, final String password) {
+        super(username, password);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/client/lib/src/main/java/org/apache/syncope/client/lib/AuthenticationHandler.java
----------------------------------------------------------------------
diff --git a/client/lib/src/main/java/org/apache/syncope/client/lib/AuthenticationHandler.java b/client/lib/src/main/java/org/apache/syncope/client/lib/AuthenticationHandler.java
new file mode 100644
index 0000000..d30de3d
--- /dev/null
+++ b/client/lib/src/main/java/org/apache/syncope/client/lib/AuthenticationHandler.java
@@ -0,0 +1,27 @@
+/*
+ * 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.client.lib;
+
+/**
+ * Marker interface for usage with
+ * {@link SyncopeClientFactoryBean#create(org.apache.syncope.client.lib.AuthenticationHandler)}.
+ */
+public interface AuthenticationHandler {
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/client/lib/src/main/java/org/apache/syncope/client/lib/BasicAuthenticationHandler.java
----------------------------------------------------------------------
diff --git a/client/lib/src/main/java/org/apache/syncope/client/lib/BasicAuthenticationHandler.java b/client/lib/src/main/java/org/apache/syncope/client/lib/BasicAuthenticationHandler.java
new file mode 100644
index 0000000..ff1452e
--- /dev/null
+++ b/client/lib/src/main/java/org/apache/syncope/client/lib/BasicAuthenticationHandler.java
@@ -0,0 +1,43 @@
+/*
+ * 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.client.lib;
+
+/**
+ * Implementation providing Basic Authentication capability.
+ */
+public class BasicAuthenticationHandler implements AuthenticationHandler {
+
+    private final String username;
+
+    private final String password;
+
+    public BasicAuthenticationHandler(final String username, final String password) {
+        this.username = username;
+        this.password = password;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/client/lib/src/main/java/org/apache/syncope/client/lib/JWTAuthenticationHandler.java
----------------------------------------------------------------------
diff --git a/client/lib/src/main/java/org/apache/syncope/client/lib/JWTAuthenticationHandler.java b/client/lib/src/main/java/org/apache/syncope/client/lib/JWTAuthenticationHandler.java
new file mode 100644
index 0000000..b13ec2e
--- /dev/null
+++ b/client/lib/src/main/java/org/apache/syncope/client/lib/JWTAuthenticationHandler.java
@@ -0,0 +1,36 @@
+/*
+ * 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.client.lib;
+
+/**
+ * Implementation providing JSON Web Token authentication capability.
+ */
+public class JWTAuthenticationHandler implements AuthenticationHandler {
+
+    private final String jwt;
+
+    public JWTAuthenticationHandler(final String jwtToken) {
+        this.jwt = jwtToken;
+    }
+
+    public String getJwt() {
+        return jwt;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/client/lib/src/main/java/org/apache/syncope/client/lib/NoAuthenticationHandler.java
----------------------------------------------------------------------
diff --git a/client/lib/src/main/java/org/apache/syncope/client/lib/NoAuthenticationHandler.java b/client/lib/src/main/java/org/apache/syncope/client/lib/NoAuthenticationHandler.java
new file mode 100644
index 0000000..6ad2b1f
--- /dev/null
+++ b/client/lib/src/main/java/org/apache/syncope/client/lib/NoAuthenticationHandler.java
@@ -0,0 +1,26 @@
+/*
+ * 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.client.lib;
+
+/**
+ * Empty implementation not providing any real authentication capability.
+ */
+public class NoAuthenticationHandler implements AuthenticationHandler {
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/client/lib/src/main/java/org/apache/syncope/client/lib/RestClientFactoryBean.java
----------------------------------------------------------------------
diff --git a/client/lib/src/main/java/org/apache/syncope/client/lib/RestClientFactoryBean.java b/client/lib/src/main/java/org/apache/syncope/client/lib/RestClientFactoryBean.java
deleted file mode 100644
index c126deb..0000000
--- a/client/lib/src/main/java/org/apache/syncope/client/lib/RestClientFactoryBean.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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.client.lib;
-
-import javax.ws.rs.core.MediaType;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.cxf.jaxrs.client.Client;
-import org.apache.cxf.jaxrs.client.ClientConfiguration;
-import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
-import org.apache.cxf.jaxrs.client.WebClient;
-import org.apache.cxf.transport.common.gzip.GZIPInInterceptor;
-import org.apache.cxf.transport.common.gzip.GZIPOutInterceptor;
-import org.apache.cxf.transport.http.URLConnectionHTTPConduit;
-
-/**
- * Provides shortcuts for creating JAX-RS service instances via CXF's {@link JAXRSClientFactoryBean}.
- */
-public class RestClientFactoryBean extends JAXRSClientFactoryBean {
-
-    public static final String HEADER_SPLIT_PROPERTY = "org.apache.cxf.http.header.split";
-
-    /**
-     * Creates an anonymous instance of the given service class, for the given content type.
-     *
-     * @param <T> any service class
-     * @param serviceClass service class reference
-     * @param mediaType XML or JSON are supported
-     * @return anonymous service instance of the given reference class
-     */
-    public <T> T createServiceInstance(final Class<T> serviceClass, final MediaType mediaType) {
-        return createServiceInstance(serviceClass, mediaType, null, null, false);
-    }
-
-    /**
-     * Creates an authenticated instance of the given service class, for the given content type.
-     *
-     * @param <T> any service class
-     * @param serviceClass service class reference
-     * @param mediaType XML or JSON are supported
-     * @param username username for REST authentication
-     * @param password password for REST authentication
-     * @param useCompression whether transparent gzip <tt>Content-Encoding</tt> handling is to be enabled
-     * @return anonymous service instance of the given reference class
-     */
-    public <T> T createServiceInstance(
-            final Class<T> serviceClass,
-            final MediaType mediaType,
-            final String username,
-            final String password,
-            final boolean useCompression) {
-
-        if (StringUtils.isNotBlank(username)) {
-            setUsername(username);
-        }
-        if (StringUtils.isNotBlank(password)) {
-            setPassword(password);
-        }
-
-        setServiceClass(serviceClass);
-        T serviceInstance = create(serviceClass);
-
-        Client client = WebClient.client(serviceInstance);
-        client.type(mediaType).accept(mediaType);
-
-        ClientConfiguration config = WebClient.getConfig(client);
-        config.getRequestContext().put(HEADER_SPLIT_PROPERTY, true);
-        config.getRequestContext().put(URLConnectionHTTPConduit.HTTPURL_CONNECTION_METHOD_REFLECTION, true);
-        if (useCompression) {
-            config.getInInterceptors().add(new GZIPInInterceptor());
-            config.getOutInterceptors().add(new GZIPOutInterceptor());
-        }
-
-        return serviceInstance;
-    }
-}

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java
----------------------------------------------------------------------
diff --git a/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java b/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java
index 7c61b10..a566419 100644
--- a/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java
+++ b/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java
@@ -21,7 +21,9 @@ package org.apache.syncope.client.lib;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import java.io.IOException;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import javax.ws.rs.core.EntityTag;
@@ -29,7 +31,14 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import org.apache.commons.lang3.tuple.ImmutablePair;
 import org.apache.commons.lang3.tuple.Pair;
+import org.apache.cxf.jaxrs.client.Client;
+import org.apache.cxf.jaxrs.client.ClientConfiguration;
+import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
 import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.cxf.transport.common.gzip.GZIPInInterceptor;
+import org.apache.cxf.transport.common.gzip.GZIPOutInterceptor;
+import org.apache.cxf.transport.http.URLConnectionHTTPConduit;
+import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.search.AnyObjectFiqlSearchConditionBuilder;
 import org.apache.syncope.common.lib.search.OrderByClauseBuilder;
 import org.apache.syncope.common.lib.search.GroupFiqlSearchConditionBuilder;
@@ -37,6 +46,7 @@ import org.apache.syncope.common.lib.search.UserFiqlSearchConditionBuilder;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.rest.api.Preference;
 import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.service.AccessTokenService;
 import org.apache.syncope.common.rest.api.service.UserSelfService;
 
 /**
@@ -45,34 +55,99 @@ import org.apache.syncope.common.rest.api.service.UserSelfService;
  */
 public class SyncopeClient {
 
+    private static final String HEADER_SPLIT_PROPERTY = "org.apache.cxf.http.header.split";
+
     private final MediaType mediaType;
 
-    private final RestClientFactoryBean restClientFactory;
+    private final JAXRSClientFactoryBean restClientFactory;
 
     private final RestClientExceptionMapper exceptionMapper;
 
-    private final String username;
-
-    private final String password;
-
     private final boolean useCompression;
 
     public SyncopeClient(
             final MediaType mediaType,
-            final RestClientFactoryBean restClientFactory,
+            final JAXRSClientFactoryBean restClientFactory,
             final RestClientExceptionMapper exceptionMapper,
-            final String username, final String password,
+            final AuthenticationHandler handler,
             final boolean useCompression) {
 
         this.mediaType = mediaType;
         this.restClientFactory = restClientFactory;
+        if (this.restClientFactory.getHeaders() == null) {
+            this.restClientFactory.setHeaders(new HashMap<String, String>());
+        }
         this.exceptionMapper = exceptionMapper;
-        this.username = username;
-        this.password = password;
+        init(handler);
         this.useCompression = useCompression;
     }
 
     /**
+     * Initializes the provided {@code restClientFactory} with the authentication capabilities of the provided
+     * {@code handler}.
+     *
+     * Currently supports:
+     * <ul>
+     * <li>{@link JWTAuthenticationHandler}</li>
+     * <li>{@link AnonymousAuthenticationHandler}</li>
+     * <li>{@link BasicAuthenticationHandler}</li>
+     * </ul>
+     * More can be supported by subclasses.
+     *
+     * @param handler authentication handler
+     */
+    protected void init(final AuthenticationHandler handler) {
+        cleanup();
+
+        if (handler instanceof AnonymousAuthenticationHandler) {
+            restClientFactory.setUsername(((AnonymousAuthenticationHandler) handler).getUsername());
+            restClientFactory.setPassword(((AnonymousAuthenticationHandler) handler).getPassword());
+        } else if (handler instanceof BasicAuthenticationHandler) {
+            restClientFactory.setUsername(((BasicAuthenticationHandler) handler).getUsername());
+            restClientFactory.setPassword(((BasicAuthenticationHandler) handler).getPassword());
+
+            String jwt = getService(AccessTokenService.class).login().getHeaderString(RESTHeaders.TOKEN);
+            restClientFactory.getHeaders().put(RESTHeaders.TOKEN, Collections.singletonList(jwt));
+
+            restClientFactory.setUsername(null);
+            restClientFactory.setPassword(null);
+        } else if (handler instanceof JWTAuthenticationHandler) {
+            restClientFactory.getHeaders().put(
+                    RESTHeaders.TOKEN, Collections.singletonList(((JWTAuthenticationHandler) handler).getJwt()));
+        }
+    }
+
+    protected void cleanup() {
+        restClientFactory.getHeaders().remove(RESTHeaders.TOKEN);
+        restClientFactory.setUsername(null);
+        restClientFactory.setPassword(null);
+    }
+
+    /**
+     * Attempts to extend the lifespan of the JWT currently in use.
+     */
+    public void refresh() {
+        getService(AccessTokenService.class).refresh();
+    }
+
+    /**
+     * Invalidates the JWT currently in use.
+     */
+    public void logout() {
+        getService(AccessTokenService.class).logout();
+        cleanup();
+    }
+
+    /**
+     * (Re)initializes the current instance with the authentication capabilities of the provided {@code handler}.
+     *
+     * @param handler authentication handler
+     */
+    public void login(final AuthenticationHandler handler) {
+        init(handler);
+    }
+
+    /**
      * Returns a new instance of {@link UserFiqlSearchConditionBuilder}, for assisted building of FIQL queries.
      *
      * @return default instance of {@link UserFiqlSearchConditionBuilder}
@@ -110,6 +185,31 @@ public class SyncopeClient {
     }
 
     /**
+     * Returns the JWT in used by this instance, passed with the {@link RESTHeaders#TOKEN} header in all requests.
+     * It can be null (in case {@link NoAuthenticationHandler} or {@link AnonymousAuthenticationHandler} were used).
+     *
+     * @return the JWT in used by this instance
+     */
+    public String getJWT() {
+        List<String> headerValues = restClientFactory.getHeaders().get(RESTHeaders.TOKEN);
+        return headerValues == null || headerValues.isEmpty()
+                ? null
+                : headerValues.get(0);
+    }
+
+    /**
+     * Returns the domain configured for this instance, or {@link SyncopeConstants#MASTER_DOMAIN} if not set.
+     *
+     * @return the domain configured for this instance
+     */
+    public String getDomain() {
+        List<String> headerValues = restClientFactory.getHeaders().get(RESTHeaders.DOMAIN);
+        return headerValues == null || headerValues.isEmpty()
+                ? SyncopeConstants.MASTER_DOMAIN
+                : headerValues.get(0);
+    }
+
+    /**
      * Creates an instance of the given service class, with configured content type and authentication.
      *
      * @param <T> any service class
@@ -118,7 +218,21 @@ public class SyncopeClient {
      */
     public <T> T getService(final Class<T> serviceClass) {
         synchronized (restClientFactory) {
-            return restClientFactory.createServiceInstance(serviceClass, mediaType, username, password, useCompression);
+            restClientFactory.setServiceClass(serviceClass);
+            T serviceInstance = restClientFactory.create(serviceClass);
+
+            Client client = WebClient.client(serviceInstance);
+            client.type(mediaType).accept(mediaType);
+
+            ClientConfiguration config = WebClient.getConfig(client);
+            config.getRequestContext().put(HEADER_SPLIT_PROPERTY, true);
+            config.getRequestContext().put(URLConnectionHTTPConduit.HTTPURL_CONNECTION_METHOD_REFLECTION, true);
+            if (useCompression) {
+                config.getInInterceptors().add(new GZIPInInterceptor());
+                config.getOutInterceptors().add(new GZIPOutInterceptor());
+            }
+
+            return serviceInstance;
         }
     }
 
@@ -126,8 +240,7 @@ public class SyncopeClient {
     public Pair<Map<String, Set<String>>, UserTO> self() {
         // Explicitly disable header value split because it interferes with JSON deserialization below
         UserSelfService service = getService(UserSelfService.class);
-        WebClient.getConfig(WebClient.client(service)).
-                getRequestContext().put(RestClientFactoryBean.HEADER_SPLIT_PROPERTY, false);
+        WebClient.getConfig(WebClient.client(service)).getRequestContext().put(HEADER_SPLIT_PROPERTY, false);
 
         Response response = service.read();
         if (response.getStatusInfo().getStatusCode() != Response.Status.OK.getStatusCode()) {
@@ -164,19 +277,6 @@ public class SyncopeClient {
     }
 
     /**
-     * Creates an instance of the given service class and sets the given header.
-     *
-     * @param <T> any service class
-     * @param serviceClass service class reference
-     * @param key HTTP header key
-     * @param values HTTP header values
-     * @return service instance of the given reference class, with given header set
-     */
-    public <T> T header(final Class<T> serviceClass, final String key, final Object... values) {
-        return header(getService(serviceClass), key, values);
-    }
-
-    /**
      * Sets the {@code Prefer} header on the give service instance.
      *
      * @param <T> any service class
@@ -189,28 +289,16 @@ public class SyncopeClient {
     }
 
     /**
-     * Creates an instance of the given service class, with {@code Prefer} header set.
-     *
-     * @param <T> any service class
-     * @param serviceClass service class reference
-     * @param preference preference to be set via {@code Prefer} header
-     * @return service instance of the given reference class, with {@code Prefer} header set
-     */
-    public <T> T prefer(final Class<T> serviceClass, final Preference preference) {
-        return header(serviceClass, RESTHeaders.PREFER, preference.toString());
-    }
-
-    /**
      * Asks for asynchronous propagation towards external resources with null priority.
      *
      * @param <T> any service class
-     * @param serviceClass service class reference
+     * @param service service class instance
      * @param nullPriorityAsync whether asynchronous propagation towards external resources with null priority is
      * requested
      * @return service instance of the given reference class, with related header set
      */
-    public <T> T nullPriorityAsync(final Class<T> serviceClass, final boolean nullPriorityAsync) {
-        return header(serviceClass, RESTHeaders.NULL_PRIORITY_ASYNC, nullPriorityAsync);
+    public <T> T nullPriorityAsync(final T service, final boolean nullPriorityAsync) {
+        return header(service, RESTHeaders.NULL_PRIORITY_ASYNC, nullPriorityAsync);
     }
 
     /**
@@ -240,18 +328,6 @@ public class SyncopeClient {
     }
 
     /**
-     * Creates an instance of the given service class, with {@code If-Match} header set.
-     *
-     * @param <T> any service class
-     * @param serviceClass service class reference
-     * @param etag ETag value
-     * @return given service instance, with {@code If-Match} set
-     */
-    public <T> T ifMatch(final Class<T> serviceClass, final EntityTag etag) {
-        return match(getService(serviceClass), etag, false);
-    }
-
-    /**
      * Sets the {@code If-None-Match} header on the given service instance.
      *
      * @param <T> any service class
@@ -264,18 +340,6 @@ public class SyncopeClient {
     }
 
     /**
-     * Creates an instance of the given service class, with {@code If-None-Match} header set.
-     *
-     * @param <T> any service class
-     * @param serviceClass service class reference
-     * @param etag ETag value
-     * @return given service instance, with {@code If-None-Match} set
-     */
-    public <T> T ifNoneMatch(final Class<T> serviceClass, final EntityTag etag) {
-        return match(getService(serviceClass), etag, true);
-    }
-
-    /**
      * Fetches {@code ETag} header value from latest service run (if available).
      *
      * @param <T> any service class

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClientFactoryBean.java
----------------------------------------------------------------------
diff --git a/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClientFactoryBean.java b/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClientFactoryBean.java
index 6ea7b4f..c96f4d0 100644
--- a/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClientFactoryBean.java
+++ b/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClientFactoryBean.java
@@ -31,6 +31,7 @@ import javax.xml.bind.Marshaller;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.cxf.feature.Feature;
 import org.apache.cxf.feature.LoggingFeature;
+import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
 import org.apache.cxf.jaxrs.provider.JAXBElementProvider;
 import org.apache.cxf.staxutils.DocumentDepthProperties;
 import org.apache.syncope.common.lib.policy.AbstractPolicyTO;
@@ -79,7 +80,7 @@ public class SyncopeClientFactoryBean {
 
     private boolean useCompression;
 
-    private RestClientFactoryBean restClientFactoryBean;
+    private JAXRSClientFactoryBean restClientFactoryBean;
 
     protected JacksonJaxbJsonProvider defaultJsonProvider() {
         ObjectMapper objectMapper = new ObjectMapper();
@@ -111,8 +112,9 @@ public class SyncopeClientFactoryBean {
         return new RestClientExceptionMapper();
     }
 
-    protected RestClientFactoryBean defaultRestClientFactoryBean() {
-        RestClientFactoryBean defaultRestClientFactoryBean = new RestClientFactoryBean();
+    protected JAXRSClientFactoryBean defaultRestClientFactoryBean() {
+        JAXRSClientFactoryBean defaultRestClientFactoryBean = new JAXRSClientFactoryBean();
+        defaultRestClientFactoryBean.setHeaders(new HashMap<String, String>());
 
         if (StringUtils.isBlank(address)) {
             throw new IllegalArgumentException("Property 'address' is missing");
@@ -120,7 +122,7 @@ public class SyncopeClientFactoryBean {
         defaultRestClientFactoryBean.setAddress(address);
 
         if (StringUtils.isNotBlank(domain)) {
-            defaultRestClientFactoryBean.setHeaders(Collections.singletonMap(RESTHeaders.DOMAIN, domain));
+            defaultRestClientFactoryBean.getHeaders().put(RESTHeaders.DOMAIN, Collections.singletonList(domain));
         }
 
         defaultRestClientFactoryBean.setThreadSafe(true);
@@ -221,41 +223,62 @@ public class SyncopeClientFactoryBean {
         return useCompression;
     }
 
-    public RestClientFactoryBean getRestClientFactoryBean() {
+    public JAXRSClientFactoryBean getRestClientFactoryBean() {
         return restClientFactoryBean == null
                 ? defaultRestClientFactoryBean()
                 : restClientFactoryBean;
     }
 
-    public SyncopeClientFactoryBean setRestClientFactoryBean(final RestClientFactoryBean restClientFactoryBean) {
+    public SyncopeClientFactoryBean setRestClientFactoryBean(final JAXRSClientFactoryBean restClientFactoryBean) {
         this.restClientFactoryBean = restClientFactoryBean;
         return this;
     }
 
     /**
-     * Builds client instance with no authentication, for user self-registration and related queries (schema,
-     * resources, ...).
+     * Builds client instance with no authentication, for user self-registration and password reset.
      *
      * @return client instance with no authentication
      */
     public SyncopeClient create() {
-        return create(null, null);
+        return create(new NoAuthenticationHandler());
     }
 
     /**
      * Builds client instance with the given credentials.
+     * Such credentials will be used only to obtain a valid JWT in the {@link RESTHeaders#TOKEN} header;
      *
      * @param username username
      * @param password password
      * @return client instance with the given credentials
      */
     public SyncopeClient create(final String username, final String password) {
+        return create(new BasicAuthenticationHandler(username, password));
+    }
+
+    /**
+     * Builds client instance which will be passing the provided value in the {@link RESTHeaders#TOKEN}
+     * request header.
+     *
+     * @param jwt value received after login, in the {@link RESTHeaders#TOKEN} response header
+     * @return client instance which will be passing the provided value in the {{@link RESTHeaders#TOKEN}
+     * request header
+     */
+    public SyncopeClient create(final String jwt) {
+        return create(new JWTAuthenticationHandler(jwt));
+    }
+
+    /**
+     * Builds client instance with the given authentication handler.
+     *
+     * @param handler authentication handler
+     * @return client instance with the given authentication handler
+     */
+    public SyncopeClient create(final AuthenticationHandler handler) {
         return new SyncopeClient(
                 getContentType().getMediaType(),
                 getRestClientFactoryBean(),
                 getExceptionMapper(),
-                username,
-                password,
+                handler,
                 useCompression);
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/client/lib/src/test/java/org/apache/syncope/client/lib/ConcurrencyTest.java
----------------------------------------------------------------------
diff --git a/client/lib/src/test/java/org/apache/syncope/client/lib/ConcurrencyTest.java b/client/lib/src/test/java/org/apache/syncope/client/lib/ConcurrencyTest.java
index a20cb59..583109f 100644
--- a/client/lib/src/test/java/org/apache/syncope/client/lib/ConcurrencyTest.java
+++ b/client/lib/src/test/java/org/apache/syncope/client/lib/ConcurrencyTest.java
@@ -32,8 +32,7 @@ public class ConcurrencyTest {
 
     private static final int THREAD_NUMBER = 1000;
 
-    private static final SyncopeClient client =
-            new SyncopeClientFactoryBean().setAddress("http://url").create("username", "password");
+    private static final SyncopeClient client = new SyncopeClientFactoryBean().setAddress("http://url").create();
 
     @Test
     public void multiThreadTest()
@@ -54,11 +53,11 @@ public class ConcurrencyTest {
                     }
                 }
             };
-	    try {
-		execution.start();
-	    } catch(OutOfMemoryError e) {
-		// ignore
-	    }
+            try {
+                execution.start();
+            } catch (OutOfMemoryError e) {
+                // ignore
+            }
         }
 
         Thread.sleep(THREAD_NUMBER);

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/common/lib/src/main/java/org/apache/syncope/common/lib/SyncopeConstants.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/SyncopeConstants.java b/common/lib/src/main/java/org/apache/syncope/common/lib/SyncopeConstants.java
index 8d80f6d..5ab80d2 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/SyncopeConstants.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/SyncopeConstants.java
@@ -29,6 +29,8 @@ public final class SyncopeConstants {
 
     public static final String NS = "http://syncope.apache.org/2.0";
 
+    public static final String JWT_CLAIM_REMOTE_HOST = "remoteHost";
+
     public static final String MASTER_DOMAIN = "Master";
 
     public static final String ROOT_REALM = "/";

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/common/lib/src/main/java/org/apache/syncope/common/lib/to/AccessTokenTO.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/AccessTokenTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/AccessTokenTO.java
new file mode 100644
index 0000000..c172e88
--- /dev/null
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/AccessTokenTO.java
@@ -0,0 +1,88 @@
+/*
+ * 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.common.lib.to;
+
+import java.util.Date;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+
+@XmlRootElement(name = "accessToken")
+@XmlType
+public class AccessTokenTO extends AbstractBaseBean implements EntityTO {
+
+    private static final long serialVersionUID = 6577639976115661357L;
+
+    private String key;
+
+    private String body;
+
+    private Date expiryTime;
+
+    private String owner;
+
+    private String authorities;
+
+    @Override
+    public String getKey() {
+        return key;
+    }
+
+    @Override
+    public void setKey(final String key) {
+        this.key = key;
+    }
+
+    public String getBody() {
+        return body;
+    }
+
+    public void setBody(final String body) {
+        this.body = body;
+    }
+
+    public Date getExpiryTime() {
+        return expiryTime == null
+                ? null
+                : new Date(expiryTime.getTime());
+    }
+
+    public void setExpiryTime(final Date expiryTime) {
+        this.expiryTime = expiryTime == null
+                ? null
+                : new Date(expiryTime.getTime());
+    }
+
+    public String getOwner() {
+        return owner;
+    }
+
+    public void setOwner(final String owner) {
+        this.owner = owner;
+    }
+
+    public String getAuthorities() {
+        return authorities;
+    }
+
+    public void setAuthorities(final String authorities) {
+        this.authorities = authorities;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/common/lib/src/main/java/org/apache/syncope/common/lib/types/StandardEntitlement.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/types/StandardEntitlement.java b/common/lib/src/main/java/org/apache/syncope/common/lib/types/StandardEntitlement.java
index 74c59b7..8f70ba1 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/types/StandardEntitlement.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/types/StandardEntitlement.java
@@ -240,6 +240,10 @@ public final class StandardEntitlement {
 
     public static final String SECURITY_QUESTION_DELETE = "SECURITY_QUESTION_DELETE";
 
+    public static final String ACCESS_TOKEN_LIST = "TASK_LIST";
+
+    public static final String ACCESS_TOKEN_DELETE = "TASK_DELETE";
+
     private static final Set<String> VALUES;
 
     static {

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/RESTHeaders.java
----------------------------------------------------------------------
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/RESTHeaders.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/RESTHeaders.java
index 9312543..0c54116 100644
--- a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/RESTHeaders.java
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/RESTHeaders.java
@@ -25,6 +25,8 @@ public final class RESTHeaders {
 
     public static final String DOMAIN = "X-Syncope-Domain";
 
+    public static final String TOKEN = "X-Syncope-Token";
+
     public static final String OWNED_ENTITLEMENTS = "X-Syncope-Entitlements";
 
     public static final String RESOURCE_KEY = "X-Syncope-Key";

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AccessTokenQuery.java
----------------------------------------------------------------------
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AccessTokenQuery.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AccessTokenQuery.java
new file mode 100644
index 0000000..abdea3f
--- /dev/null
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AccessTokenQuery.java
@@ -0,0 +1,33 @@
+/*
+ * 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.common.rest.api.beans;
+
+public class AccessTokenQuery extends AbstractQuery {
+
+    private static final long serialVersionUID = -8792519310029596796L;
+
+    public static class Builder extends AbstractQuery.Builder<AccessTokenQuery, Builder> {
+
+        @Override
+        protected AccessTokenQuery newInstance() {
+            return new AccessTokenQuery();
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AccessTokenService.java
----------------------------------------------------------------------
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AccessTokenService.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AccessTokenService.java
new file mode 100644
index 0000000..e9f5ff3
--- /dev/null
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AccessTokenService.java
@@ -0,0 +1,82 @@
+/*
+ * 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.common.rest.api.service;
+
+import javax.ws.rs.BeanParam;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.Response;
+import org.apache.syncope.common.lib.to.AccessTokenTO;
+import org.apache.syncope.common.lib.to.PagedResult;
+import org.apache.syncope.common.rest.api.beans.AccessTokenQuery;
+
+/**
+ * REST operations for access tokens.
+ */
+@Path("accessTokens")
+public interface AccessTokenService extends JAXRSService {
+
+    /**
+     * Returns an empty response bearing the X-Syncope-Token header value, in case of successful authentication.
+     * The provided value is a signed JSON Web Token.
+     *
+     * @return empty response bearing the X-Syncope-Token header value, in case of successful authentication
+     */
+    @POST
+    @Path("login")
+    Response login();
+
+    /**
+     * Returns an empty response bearing the X-Syncope-Token header value, with extended lifetime.
+     * The provided value is a signed JSON Web Token.
+     *
+     * @return an empty response bearing the X-Syncope-Token header value, with extended lifetime
+     */
+    @POST
+    @Path("refresh")
+    Response refresh();
+
+    /**
+     * Invalidates the access token of the requesting user.
+     */
+    @POST
+    @Path("logout")
+    void logout();
+
+    /**
+     * Returns a paged list of existing access tokens matching the given query.
+     *
+     * @param query query conditions
+     * @return paged list of existing access tokens matching the given query
+     */
+    @GET
+    PagedResult<AccessTokenTO> list(@BeanParam AccessTokenQuery query);
+
+    /**
+     * Invalidates the access token matching the provided key.
+     *
+     * @param key access token key
+     */
+    @DELETE
+    @Path("{key}")
+    void delete(@PathParam("key") String key);
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/logic/pom.xml
----------------------------------------------------------------------
diff --git a/core/logic/pom.xml b/core/logic/pom.xml
index 6828b5b..7603b58 100644
--- a/core/logic/pom.xml
+++ b/core/logic/pom.xml
@@ -39,6 +39,16 @@ under the License.
 
   <dependencies>
     <dependency>
+      <groupId>org.apache.cxf</groupId>
+      <artifactId>cxf-rt-rs-security-jose</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>com.fasterxml.uuid</groupId>
+      <artifactId>java-uuid-generator</artifactId>
+    </dependency>
+
+    <dependency>
       <groupId>org.apache.commons</groupId>
       <artifactId>commons-dbcp2</artifactId>
     </dependency>

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/logic/src/main/java/org/apache/syncope/core/logic/AccessTokenLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/AccessTokenLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/AccessTokenLogic.java
new file mode 100644
index 0000000..6ca1b87
--- /dev/null
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/AccessTokenLogic.java
@@ -0,0 +1,187 @@
+/*
+ * 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.logic;
+
+import com.fasterxml.uuid.Generators;
+import com.fasterxml.uuid.impl.RandomBasedGenerator;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import javax.annotation.Resource;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.Transformer;
+import org.apache.cxf.rs.security.jose.common.JoseType;
+import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm;
+import org.apache.cxf.rs.security.jose.jws.JwsHeaders;
+import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactConsumer;
+import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactProducer;
+import org.apache.cxf.rs.security.jose.jws.JwsSignatureProvider;
+import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
+import org.apache.cxf.rs.security.jose.jwt.JwtToken;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.to.AccessTokenTO;
+import org.apache.syncope.common.lib.types.StandardEntitlement;
+import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
+import org.apache.syncope.core.persistence.api.dao.ConfDAO;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.core.persistence.api.entity.AccessToken;
+import org.apache.syncope.core.provisioning.api.data.AccessTokenDataBinder;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+
+@Component
+public class AccessTokenLogic extends AbstractTransactionalLogic<AccessTokenTO> {
+
+    private static final RandomBasedGenerator UUID_GENERATOR = Generators.randomBasedGenerator();
+
+    private static final JwsHeaders JWS_HEADERS = new JwsHeaders(JoseType.JWT, SignatureAlgorithm.HS512);
+
+    @Resource(name = "jwtIssuer")
+    private String jwtIssuer;
+
+    @Resource(name = "anonymousUser")
+    private String anonymousUser;
+
+    @Autowired
+    private JwsSignatureProvider jwsSignatureProvider;
+
+    @Autowired
+    private AccessTokenDataBinder binder;
+
+    @Autowired
+    private AccessTokenDAO accessTokenDAO;
+
+    @Autowired
+    private ConfDAO confDAO;
+
+    @PreAuthorize("isAuthenticated()")
+    public String login(final String remoteHost) {
+        if (anonymousUser.equals(AuthContextUtils.getUsername())) {
+            throw new IllegalArgumentException(anonymousUser + " cannot be granted for an access token");
+        }
+
+        String body = null;
+
+        AccessToken accessToken = accessTokenDAO.findByOwner(AuthContextUtils.getUsername());
+        if (accessToken != null) {
+            body = accessToken.getBody();
+        }
+
+        if (body == null) {
+            Date now = new Date();
+            Calendar expiry = Calendar.getInstance();
+            expiry.setTime(now);
+            expiry.add(Calendar.MINUTE,
+                    confDAO.find("jwt.lifetime.minutes", "120").getValues().get(0).getLongValue().intValue());
+
+            JwtClaims claims = new JwtClaims();
+            claims.setTokenId(UUID_GENERATOR.generate().toString());
+            claims.setSubject(AuthContextUtils.getUsername());
+            claims.setIssuedAt(now.getTime());
+            claims.setIssuer(jwtIssuer);
+            claims.setExpiryTime(expiry.getTime().getTime());
+            claims.setNotBefore(now.getTime());
+            claims.setClaim(SyncopeConstants.JWT_CLAIM_REMOTE_HOST, remoteHost);
+
+            JwtToken token = new JwtToken(JWS_HEADERS, claims);
+            JwsJwtCompactProducer producer = new JwsJwtCompactProducer(token);
+
+            body = producer.signWith(jwsSignatureProvider);
+
+            binder.create(claims.getTokenId(), body, expiry.getTime());
+        }
+
+        return body;
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    public String refresh() {
+        AccessToken accessToken = accessTokenDAO.findByOwner(AuthContextUtils.getUsername());
+        if (accessToken == null) {
+            throw new NotFoundException("AccessToken for " + AuthContextUtils.getUsername());
+        }
+
+        JwsJwtCompactConsumer consumer = new JwsJwtCompactConsumer(accessToken.getBody());
+
+        Date now = new Date();
+        Calendar expiry = Calendar.getInstance();
+        expiry.setTime(now);
+        expiry.add(Calendar.MINUTE,
+                confDAO.find("jwt.lifetime.minutes", "120").getValues().get(0).getLongValue().intValue());
+        consumer.getJwtClaims().setExpiryTime(expiry.getTime().getTime());
+
+        JwtToken token = new JwtToken(JWS_HEADERS, consumer.getJwtClaims());
+        JwsJwtCompactProducer producer = new JwsJwtCompactProducer(token);
+
+        String body = producer.signWith(jwsSignatureProvider);
+
+        binder.update(accessToken, body, expiry.getTime());
+
+        return body;
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    public void logout() {
+        AccessToken accessToken = accessTokenDAO.findByOwner(AuthContextUtils.getUsername());
+        if (accessToken == null) {
+            throw new NotFoundException("AccessToken for " + AuthContextUtils.getUsername());
+        }
+
+        delete(accessToken.getKey());
+    }
+
+    @PreAuthorize("hasRole('" + StandardEntitlement.ACCESS_TOKEN_LIST + "')")
+    public int count() {
+        return accessTokenDAO.count();
+    }
+
+    @PreAuthorize("hasRole('" + StandardEntitlement.ACCESS_TOKEN_LIST + "')")
+    public List<AccessTokenTO> list(
+            final int page,
+            final int size,
+            final List<OrderByClause> orderByClauses) {
+
+        return CollectionUtils.collect(accessTokenDAO.findAll(page, size, orderByClauses),
+                new Transformer<AccessToken, AccessTokenTO>() {
+
+            @Override
+            public AccessTokenTO transform(final AccessToken input) {
+                return binder.getAccessTokenTO(input);
+            }
+        }, new ArrayList<AccessTokenTO>());
+    }
+
+    @PreAuthorize("hasRole('" + StandardEntitlement.ACCESS_TOKEN_DELETE + "')")
+    public void delete(final String key) {
+        accessTokenDAO.delete(key);
+    }
+
+    @Override
+    protected AccessTokenTO resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+
+        throw new UnresolvedReferenceException();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java
index 033dfa3..af8a29f 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java
@@ -137,8 +137,7 @@ public class ReportLogic extends AbstractExecutableLogic<ReportTO> {
 
     @PreAuthorize("hasRole('" + StandardEntitlement.REPORT_LIST + "')")
     public List<ReportTO> list() {
-        return CollectionUtils.collect(reportDAO.findAll(),
-                new Transformer<Report, ReportTO>() {
+        return CollectionUtils.collect(reportDAO.findAll(), new Transformer<Report, ReportTO>() {
 
             @Override
             public ReportTO transform(final Report input) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AccessTokenDAO.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AccessTokenDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AccessTokenDAO.java
new file mode 100644
index 0000000..ef07ee6
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AccessTokenDAO.java
@@ -0,0 +1,42 @@
+/*
+ * 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.persistence.api.dao;
+
+import java.util.List;
+import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.core.persistence.api.entity.AccessToken;
+
+public interface AccessTokenDAO extends DAO<AccessToken> {
+
+    AccessToken find(String key);
+
+    AccessToken findByOwner(String username);
+
+    int count();
+
+    List<AccessToken> findAll(int page, int itemsPerPage, List<OrderByClause> orderByClauses);
+
+    AccessToken save(AccessToken accessToken);
+
+    void delete(String key);
+
+    void delete(AccessToken accessToken);
+
+    int deleteExpired();
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AccessToken.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AccessToken.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AccessToken.java
new file mode 100644
index 0000000..e08e9e3
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AccessToken.java
@@ -0,0 +1,36 @@
+/*
+ * 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.persistence.api.entity;
+
+import java.util.Date;
+
+public interface AccessToken extends ProvidedKeyEntity {
+
+    String getBody();
+
+    void setBody(String body);
+
+    Date getExpiryTime();
+
+    void setExpiryTime(Date expiryTime);
+
+    String getOwner();
+
+    void setOwner(String owner);
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAccessTokenDAO.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAccessTokenDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAccessTokenDAO.java
new file mode 100644
index 0000000..01c53fd
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAccessTokenDAO.java
@@ -0,0 +1,143 @@
+/*
+ * 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.persistence.jpa.dao;
+
+import java.util.Date;
+import java.util.List;
+import javax.persistence.NoResultException;
+import javax.persistence.Query;
+import javax.persistence.TypedQuery;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
+import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.core.persistence.api.entity.AccessToken;
+import org.apache.syncope.core.persistence.jpa.entity.JPAAccessToken;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.ReflectionUtils;
+
+@Repository
+public class JPAAccessTokenDAO extends AbstractDAO<AccessToken> implements AccessTokenDAO {
+
+    @Transactional(readOnly = true)
+    @Override
+    public AccessToken find(final String key) {
+        return entityManager().find(JPAAccessToken.class, key);
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public AccessToken findByOwner(final String username) {
+        TypedQuery<AccessToken> query = entityManager().createQuery(
+                "SELECT e FROM " + JPAAccessToken.class.getSimpleName() + " e "
+                + "WHERE e.owner=:username", AccessToken.class);
+        query.setParameter("username", username);
+
+        AccessToken result = null;
+        try {
+            result = query.getSingleResult();
+        } catch (NoResultException e) {
+            LOG.debug("No token for user {} could be found", username, e);
+        }
+
+        return result;
+    }
+
+    private StringBuilder buildFindAllQuery() {
+        return new StringBuilder("SELECT e FROM ").
+                append(JPAAccessToken.class.getSimpleName()).
+                append(" e WHERE 1=1");
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public int count() {
+        StringBuilder queryString = buildFindAllQuery();
+
+        Query query = entityManager().createQuery(StringUtils.replaceOnce(
+                queryString.toString(), "SELECT e", "SELECT COUNT(e)"));
+        return ((Number) query.getSingleResult()).intValue();
+    }
+
+    private String toOrderByStatement(final List<OrderByClause> orderByClauses) {
+        StringBuilder statement = new StringBuilder();
+
+        for (OrderByClause clause : orderByClauses) {
+            String field = clause.getField().trim();
+            if (ReflectionUtils.findField(JPAAccessToken.class, field) != null) {
+                statement.append("e.").append(field).append(' ').append(clause.getDirection().name());
+            }
+        }
+
+        if (statement.length() == 0) {
+            statement.append("ORDER BY e.expiryTime DESC");
+        } else {
+            statement.insert(0, "ORDER BY ");
+        }
+        return statement.toString();
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public List<AccessToken> findAll(final int page, final int itemsPerPage, final List<OrderByClause> orderByClauses) {
+        StringBuilder queryString = buildFindAllQuery().append(toOrderByStatement(orderByClauses));
+
+        TypedQuery<AccessToken> query = entityManager().createQuery(queryString.toString(), AccessToken.class);
+
+        query.setFirstResult(itemsPerPage * (page <= 0
+                ? 0
+                : page - 1));
+
+        if (itemsPerPage > 0) {
+            query.setMaxResults(itemsPerPage);
+        }
+
+        return query.getResultList();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Throwable.class)
+    public AccessToken save(final AccessToken accessToken) {
+        return entityManager().merge(accessToken);
+    }
+
+    @Override
+    public void delete(final String key) {
+        AccessToken accessToken = find(key);
+        if (accessToken == null) {
+            return;
+        }
+
+        delete(accessToken);
+    }
+
+    @Override
+    public void delete(final AccessToken accessToken) {
+        entityManager().remove(accessToken);
+    }
+
+    @Override
+    public int deleteExpired() {
+        Query query = entityManager().createQuery(
+                "DELETE FROM " + JPAAccessToken.class.getSimpleName() + " e "
+                + "WHERE e.expiryTime < :now");
+        query.setParameter("now", new Date());
+        return query.executeUpdate();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
index 039c279..3580136 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
@@ -50,6 +50,7 @@ import org.apache.syncope.core.spring.security.DelegatedAdministrationException;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.apache.syncope.core.persistence.api.ImplementationLookup;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException;
+import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
 import org.apache.syncope.core.persistence.api.dao.AccountRule;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
@@ -57,6 +58,7 @@ import org.apache.syncope.core.persistence.api.dao.PasswordRule;
 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
 import org.apache.syncope.core.persistence.api.dao.RoleDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.entity.AccessToken;
 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
 import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.Role;
@@ -96,6 +98,9 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
     private RoleDAO roleDAO;
 
     @Autowired
+    private AccessTokenDAO accessTokenDAO;
+
+    @Autowired
     private ImplementationLookup implementationLookup;
 
     @Resource(name = "adminUser")
@@ -424,6 +429,11 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
             group.getUDynMembership().getMembers().remove(user);
         }
 
+        AccessToken accessToken = accessTokenDAO.findByOwner(user.getUsername());
+        if (accessToken != null) {
+            accessTokenDAO.delete(accessToken);
+        }
+
         entityManager().remove(user);
     }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAccessToken.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAccessToken.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAccessToken.java
new file mode 100644
index 0000000..05464d6
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAccessToken.java
@@ -0,0 +1,83 @@
+/*
+ * 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.persistence.jpa.entity;
+
+import java.util.Date;
+import javax.persistence.Cacheable;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Lob;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import org.apache.syncope.core.persistence.api.entity.AccessToken;
+
+@Entity
+@Table(name = JPAAccessToken.TABLE)
+@Cacheable
+public class JPAAccessToken extends AbstractProvidedKeyEntity implements AccessToken {
+
+    public static final String TABLE = "AccessToken";
+
+    private static final long serialVersionUID = -8734194815582467949L;
+
+    @Lob
+    private String body;
+
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date expiryTime;
+
+    @Column(nullable = true)
+    private String owner;
+
+    @Override
+    public String getBody() {
+        return body;
+    }
+
+    @Override
+    public void setBody(final String body) {
+        this.body = body;
+    }
+
+    @Override
+    public Date getExpiryTime() {
+        return expiryTime == null
+                ? null
+                : new Date(expiryTime.getTime());
+    }
+
+    @Override
+    public void setExpiryTime(final Date expiryTime) {
+        this.expiryTime = expiryTime == null
+                ? null
+                : new Date(expiryTime.getTime());
+    }
+
+    @Override
+    public String getOwner() {
+        return owner;
+    }
+
+    @Override
+    public void setOwner(final String owner) {
+        this.owner = owner;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
index 30483fc..7d9660c 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.persistence.jpa.entity;
 
+import org.apache.syncope.core.persistence.api.entity.AccessToken;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPasswordPolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPullPolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAccountPolicy;
@@ -252,6 +253,8 @@ public class JPAEntityFactory implements EntityFactory {
             result = (E) new JPAADynGroupMembership();
         } else if (reference.equals(UDynGroupMembership.class)) {
             result = (E) new JPAUDynGroupMembership();
+        } else if (reference.equals(AccessToken.class)) {
+            result = (E) new JPAAccessToken();
         } else {
             throw new IllegalArgumentException("Could not find a JPA implementation of " + reference.getName());
         }


Mime
View raw message