http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/persistence-jpa/src/main/resources/domains/MasterContent.xml
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/resources/domains/MasterContent.xml 
b/core/persistence-jpa/src/main/resources/domains/MasterContent.xml
index 6aca424..33d8a70 100644
--- a/core/persistence-jpa/src/main/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa/src/main/resources/domains/MasterContent.xml
@@ -142,6 +142,15 @@ under the License.
               owner_id="cd64d66f-6fff-4008-b966-a06b1cc1436d" 
schema_id="identity.recertification.day.interval"/>
   <CPlainAttrValue id="e5fa94db-b524-4309-908d-8198d0b3f77b"
                    attribute_id="bcfd7efc-0605-4b5e-b4bb-85c1d5f64bbb" 
longValue="-1"/>
+
+  <!--  JWT lifetime in minutes -->                   
+  <SyncopeSchema id="jwt.lifetime.minutes"/>
+  <PlainSchema id="jwt.lifetime.minutes" type="Long"
+               mandatoryCondition="true" multivalue="0" uniqueConstraint="0" 
readonly="0"/>
+  <CPlainAttr id="cfec3140-562d-459c-ac6a-e3e10758661d"
+              owner_id="cd64d66f-6fff-4008-b966-a06b1cc1436d" 
schema_id="jwt.lifetime.minutes"/>
+  <CPlainAttrValue id="447e2456-3ff5-41bc-8ff1-cbb0567546cb"
+                   attribute_id="cfec3140-562d-459c-ac6a-e3e10758661d" 
longValue="120"/>
   
   <AnyType id="USER" kind="USER"/>
   <AnyTypeClass id="BaseUser"/>
@@ -160,6 +169,10 @@ under the License.
   <Task DTYPE="SchedTask" id="e95555d2-1b09-42c8-b25b-f4c4ec598989" 
name="Identity Recertification Task"  active="0"
         
jobDelegateClassName="org.apache.syncope.core.provisioning.java.job.IdentityRecertification"/>
   
+  <Task DTYPE="SchedTask" id="89de5014-e3f5-4462-84d8-d97575740baf" 
name="Access Token Cleanup Task"  active="1"
+        
jobDelegateClassName="org.apache.syncope.core.provisioning.java.job.ExpiredAccessTokenCleanup"
+        cronExpression="0 0/5 * * * ?"/>
+
   <!-- Password reset notifications -->
   <MailTemplate id="requestPasswordReset"
                 textTemplate="Hi,

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/MultitenancyTest.java
----------------------------------------------------------------------
diff --git 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/MultitenancyTest.java
 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/MultitenancyTest.java
index e82efa6..78dd527 100644
--- 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/MultitenancyTest.java
+++ 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/MultitenancyTest.java
@@ -83,7 +83,7 @@ public class MultitenancyTest extends AbstractTest {
 
     @Test
     public void readPlainSchemas() {
-        assertEquals(11, plainSchemaDAO.findAll().size());
+        assertEquals(13, plainSchemaDAO.findAll().size());
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainSchemaTest.java
----------------------------------------------------------------------
diff --git 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainSchemaTest.java
 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainSchemaTest.java
index b20bc4b..f1d0f95 100644
--- 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainSchemaTest.java
+++ 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainSchemaTest.java
@@ -47,7 +47,7 @@ public class PlainSchemaTest extends AbstractTest {
     @Test
     public void findAll() {
         List<PlainSchema> schemas = plainSchemaDAO.findAll();
-        assertEquals(40, schemas.size());
+        assertEquals(41, schemas.size());
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java
----------------------------------------------------------------------
diff --git 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java
 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java
index c77b312..e7f1165 100644
--- 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java
+++ 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java
@@ -97,7 +97,7 @@ public class TaskTest extends AbstractTest {
     public void findAll() {
         assertEquals(5, taskDAO.findAll(TaskType.PROPAGATION).size());
         assertEquals(1, taskDAO.findAll(TaskType.NOTIFICATION).size());
-        assertEquals(2, taskDAO.findAll(TaskType.SCHEDULED).size());
+        assertEquals(3, taskDAO.findAll(TaskType.SCHEDULED).size());
         assertEquals(10, taskDAO.findAll(TaskType.PULL).size());
         assertEquals(11, taskDAO.findAll(TaskType.PUSH).size());
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/AccessTokenTest.java
----------------------------------------------------------------------
diff --git 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/AccessTokenTest.java
 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/AccessTokenTest.java
new file mode 100644
index 0000000..5f87771
--- /dev/null
+++ 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/AccessTokenTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.outer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.util.Date;
+import java.util.UUID;
+import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
+import org.apache.syncope.core.persistence.api.entity.AccessToken;
+import org.apache.syncope.core.persistence.jpa.AbstractTest;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional("Master")
+public class AccessTokenTest extends AbstractTest {
+
+    @Autowired
+    private AccessTokenDAO accessTokenDAO;
+
+    @Test
+    public void crud() {
+        AccessToken accessToken = entityFactory.newEntity(AccessToken.class);
+        accessToken.setKey(UUID.randomUUID().toString());
+        accessToken.setBody("pointless body");
+        accessToken.setExpiryTime(new Date());
+        accessToken.setOwner("bellini");
+
+        accessToken = accessTokenDAO.save(accessToken);
+        assertNotNull(accessToken);
+
+        accessTokenDAO.flush();
+
+        accessToken = accessTokenDAO.findByOwner("bellini");
+        assertNotNull(accessToken);
+        assertEquals("bellini", accessToken.getOwner());
+
+        accessTokenDAO.deleteExpired();
+
+        accessTokenDAO.flush();
+
+        accessToken = accessTokenDAO.findByOwner("bellini");
+        assertNull(accessToken);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml 
b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
index 9b70be9..e626c64 100644
--- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
@@ -143,6 +143,15 @@ under the License.
   <CPlainAttrValue id="e5fa94db-b524-4309-908d-8198d0b3f77b"
                    attribute_id="bcfd7efc-0605-4b5e-b4bb-85c1d5f64bbb" 
longValue="365"/>
 
+  <!--  JWT lifetime in minutes -->                   
+  <SyncopeSchema id="jwt.lifetime.minutes"/>
+  <PlainSchema id="jwt.lifetime.minutes" type="Long"
+               mandatoryCondition="true" multivalue="0" uniqueConstraint="0" 
readonly="0"/>
+  <CPlainAttr id="cfec3140-562d-459c-ac6a-e3e10758661d"
+              owner_id="cd64d66f-6fff-4008-b966-a06b1cc1436d" 
schema_id="jwt.lifetime.minutes"/>
+  <CPlainAttrValue id="447e2456-3ff5-41bc-8ff1-cbb0567546cb"
+                   attribute_id="cfec3140-562d-459c-ac6a-e3e10758661d" 
longValue="120"/>
+
   <!-- sample policies -->
   <PullPolicy id="66691e96-285f-4464-bc19-e68384ea4c85" description="a pull 
policy"
               specification='{"conflictResolutionAction":"IGNORE"}'/>
@@ -1110,6 +1119,9 @@ under the License.
         
jobDelegateClassName="org.apache.syncope.fit.core.reference.TestSampleJobDelegate"
 cronExpression="0 0 0 1 * ?"/>
   <Task DTYPE="SchedTask" id="e95555d2-1b09-42c8-b25b-f4c4ec598989" 
name="Identity Recertification Task"  active="1"
         
jobDelegateClassName="org.apache.syncope.core.provisioning.java.job.IdentityRecertification"/>
+  <Task DTYPE="SchedTask" id="89de5014-e3f5-4462-84d8-d97575740baf" 
name="Access Token Cleanup Task"  active="1"
+        
jobDelegateClassName="org.apache.syncope.core.provisioning.java.job.ExpiredAccessTokenCleanup"
+        cronExpression="0 0/5 * * * ?"/>
   <Task DTYPE="PropagationTask" id="d6c2d6d3-6329-44c1-9187-f1469ead1cfa" 
operation="UPDATE"
         objectClassName="__ACCOUNT__" 
resource_id="ws-target-resource-nopropagation" anyTypeKind="USER" 
entityKey="1417acbe-cbf6-4277-9372-e75e04f97000"
         
attributes='[{"name":"__PASSWORD__","value":[{"readOnly":false,"disposed":false,"encryptedBytes":"m9nh2US0Sa6m+cXccCq0Xw==","base64SHA1Hash":"GFJ69qfjxEOdrmt+9q+0Cw2uz60="}]},{"name":"__NAME__","value":["userId"],"nameValue":"userId"},{"name":"fullname","value":["fullname"]},{"name":"type","value":["type"]}]'/>

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/persistence-jpa/src/test/resources/domains/TwoContent.xml
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/test/resources/domains/TwoContent.xml 
b/core/persistence-jpa/src/test/resources/domains/TwoContent.xml
index 437debc..d4949d8 100644
--- a/core/persistence-jpa/src/test/resources/domains/TwoContent.xml
+++ b/core/persistence-jpa/src/test/resources/domains/TwoContent.xml
@@ -105,6 +105,24 @@ under the License.
   <CPlainAttrValue id="e5fa94db-b524-4309-908d-8198d0b3f779"
                    attribute_id="bcfd7efc-0605-4b5e-b4bb-85c1d5f6493a" 
booleanValue="0"/>
   
+  <!-- Identity Recertification interval in days -->                   
+  <SyncopeSchema id="identity.recertification.day.interval"/>
+  <PlainSchema id="identity.recertification.day.interval" type="Long"
+               mandatoryCondition="false" multivalue="0" uniqueConstraint="0" 
readonly="0"/>
+  <CPlainAttr id="bcfd7efc-0605-4b5e-b4bb-85c1d5f64bbb"
+              owner_id="cd64d66f-6fff-4008-b966-a06b1cc1436d" 
schema_id="identity.recertification.day.interval"/>
+  <CPlainAttrValue id="e5fa94db-b524-4309-908d-8198d0b3f77b"
+                   attribute_id="bcfd7efc-0605-4b5e-b4bb-85c1d5f64bbb" 
longValue="-1"/>
+
+  <!--  JWT lifetime in minutes -->                   
+  <SyncopeSchema id="jwt.lifetime.minutes"/>
+  <PlainSchema id="jwt.lifetime.minutes" type="Long"
+               mandatoryCondition="true" multivalue="0" uniqueConstraint="0" 
readonly="0"/>
+  <CPlainAttr id="cfec3140-562d-459c-ac6a-e3e10758661d"
+              owner_id="cd64d66f-6fff-4008-b966-a06b1cc1436d" 
schema_id="jwt.lifetime.minutes"/>
+  <CPlainAttrValue id="447e2456-3ff5-41bc-8ff1-cbb0567546cb"
+                   attribute_id="cfec3140-562d-459c-ac6a-e3e10758661d" 
longValue="120"/>
+
   <AnyType id="USER" kind="USER"/>
   <AnyTypeClass id="BaseUser"/>
   <AnyType_AnyTypeClass anyType_id="USER" anyTypeClass_id="BaseUser"/>

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AccessTokenDataBinder.java
----------------------------------------------------------------------
diff --git 
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AccessTokenDataBinder.java
 
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AccessTokenDataBinder.java
new file mode 100644
index 0000000..b06b4c2
--- /dev/null
+++ 
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AccessTokenDataBinder.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.core.provisioning.api.data;
+
+import java.util.Date;
+import org.apache.syncope.common.lib.to.AccessTokenTO;
+import org.apache.syncope.core.persistence.api.entity.AccessToken;
+
+public interface AccessTokenDataBinder {
+
+    void create(String key, String body, Date expiryTime);
+
+    void update(AccessToken accessToken, String body, Date expiryTime);
+
+    AccessTokenTO getAccessTokenTO(AccessToken accessToken);
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/serialization/POJOHelper.java
----------------------------------------------------------------------
diff --git 
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/serialization/POJOHelper.java
 
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/serialization/POJOHelper.java
index 25e4316..af99906 100644
--- 
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/serialization/POJOHelper.java
+++ 
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/serialization/POJOHelper.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.core.provisioning.api.serialization;
 
 import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.module.SimpleModule;
 import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
@@ -75,6 +76,18 @@ public final class POJOHelper {
         return result;
     }
 
+    public static <T extends Object> T deserialize(final String serialized, 
final TypeReference<T> reference) {
+        T result = null;
+
+        try {
+            result = MAPPER.readValue(serialized, reference);
+        } catch (Exception e) {
+            LOG.error("During deserialization", e);
+        }
+
+        return result;
+    }
+
     private POJOHelper() {
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AccessTokenDataBinderImpl.java
----------------------------------------------------------------------
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AccessTokenDataBinderImpl.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AccessTokenDataBinderImpl.java
new file mode 100644
index 0000000..d15664c
--- /dev/null
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AccessTokenDataBinderImpl.java
@@ -0,0 +1,70 @@
+/*
+ * 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.provisioning.java.data;
+
+import java.util.Date;
+import org.apache.syncope.common.lib.to.AccessTokenTO;
+import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
+import org.apache.syncope.core.persistence.api.entity.AccessToken;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.provisioning.api.data.AccessTokenDataBinder;
+import org.apache.syncope.core.spring.BeanUtils;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class AccessTokenDataBinderImpl implements AccessTokenDataBinder {
+
+    private static final String[] IGNORE_PROPERTIES = { "owner" };
+
+    @Autowired
+    private AccessTokenDAO accessTokenDAO;
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    @Override
+    public void create(final String key, final String body, final Date 
expiryTime) {
+        AccessToken accessToken = entityFactory.newEntity(AccessToken.class);
+        accessToken.setKey(key);
+        accessToken.setBody(body);
+        accessToken.setExpiryTime(expiryTime);
+        accessToken.setOwner(AuthContextUtils.getUsername());
+
+        accessTokenDAO.save(accessToken);
+    }
+
+    @Override
+    public void update(final AccessToken accessToken, final String body, final 
Date expiryTime) {
+        accessToken.setBody(body);
+        accessToken.setExpiryTime(expiryTime);
+
+        accessTokenDAO.save(accessToken);
+    }
+
+    @Override
+    public AccessTokenTO getAccessTokenTO(final AccessToken accessToken) {
+        AccessTokenTO accessTokenTO = new AccessTokenTO();
+        BeanUtils.copyProperties(accessToken, accessTokenTO, 
IGNORE_PROPERTIES);
+        accessTokenTO.setOwner(accessToken.getOwner());
+
+        return accessTokenTO;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
----------------------------------------------------------------------
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
index 4437713..fe21b51 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
@@ -48,6 +48,7 @@ import org.apache.syncope.common.lib.types.CipherAlgorithm;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.lib.types.ResourceOperation;
+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.SecurityQuestionDAO;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
@@ -61,6 +62,7 @@ import org.apache.syncope.core.spring.BeanUtils;
 import org.apache.syncope.core.provisioning.api.utils.EntityUtils;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
 import org.apache.syncope.core.persistence.api.dao.RoleDAO;
+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.PlainSchema;
 import org.apache.syncope.core.persistence.api.entity.Realm;
@@ -99,6 +101,9 @@ public class UserDataBinderImpl extends 
AbstractAnyDataBinder implements UserDat
     @Autowired
     private AnyTypeDAO anyTypeDAO;
 
+    @Autowired
+    private AccessTokenDAO accessTokenDAO;
+
     @Resource(name = "adminUser")
     private String adminUser;
 
@@ -338,6 +343,12 @@ public class UserDataBinderImpl extends 
AbstractAnyDataBinder implements UserDat
                 
AuthContextUtils.updateUsername(userPatch.getUsername().getValue());
             }
 
+            AccessToken accessToken = accessTokenDAO.findByOwner(oldUsername);
+            if (accessToken != null) {
+                accessToken.setOwner(userPatch.getUsername().getValue());
+                accessTokenDAO.save(accessToken);
+            }
+
             propByRes.addAll(ResourceOperation.UPDATE, currentResources);
         }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/ExpiredAccessTokenCleanup.java
----------------------------------------------------------------------
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/ExpiredAccessTokenCleanup.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/ExpiredAccessTokenCleanup.java
new file mode 100644
index 0000000..1e4ac03
--- /dev/null
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/ExpiredAccessTokenCleanup.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.provisioning.java.job;
+
+import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
+import org.quartz.JobExecutionException;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public class ExpiredAccessTokenCleanup extends AbstractSchedTaskJobDelegate {
+
+    @Autowired
+    private AccessTokenDAO accessTokenDAO;
+
+    @Override
+    protected String doExecute(final boolean dryRun) throws 
JobExecutionException {
+        if (!dryRun) {
+            int deleted = accessTokenDAO.deleteExpired();
+            LOG.debug("Successfully deleted {} expired access tokens", 
deleted);
+        }
+
+        return (dryRun
+                ? "DRY "
+                : "") + "RUNNING";
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java
----------------------------------------------------------------------
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java
index 65584db..36e59a2 100755
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java
@@ -18,10 +18,13 @@
  */
 package org.apache.syncope.core.provisioning.java.job;
 
+import java.util.Collections;
 import java.util.Date;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.core.persistence.api.dao.ConfDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.entity.conf.CPlainAttr;
 import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
 import org.apache.syncope.core.persistence.api.entity.user.User;
@@ -33,6 +36,8 @@ public class IdentityRecertification extends 
AbstractSchedTaskJobDelegate {
 
     private static final String RECERTIFICATION_TIME = 
"identity.recertification.day.interval";
 
+    private static final int PAGE_SIZE = 10;
+
     @Autowired
     private ConfDAO confDAO;
 
@@ -88,13 +93,17 @@ public class IdentityRecertification extends 
AbstractSchedTaskJobDelegate {
             return ("IDENTITY RECERTIFICATION DISABLED");
         }
 
-        for (User user : userDAO.findAll()) {
-            LOG.debug("Processing user: {}", user.getUsername());
+        for (int page = 1; page <= (userDAO.count() / PAGE_SIZE) + 1; page++) {
+            for (User user : userDAO.findAll(
+                    SyncopeConstants.FULL_ADMIN_REALMS, page, PAGE_SIZE, 
Collections.<OrderByClause>emptyList())) {
 
-            if (StringUtils.isNotBlank(user.getWorkflowId()) && 
isToBeRecertified(user) && !dryRun) {
-                uwfAdapter.requestCertify(user);
-            } else {
-                LOG.warn("Workflow for {} is null or empty", user);
+                LOG.debug("Processing user: {}", user.getUsername());
+
+                if (StringUtils.isNotBlank(user.getWorkflowId()) && 
isToBeRecertified(user) && !dryRun) {
+                    uwfAdapter.requestCertify(user);
+                } else {
+                    LOG.warn("Workflow for {} is null or empty", user);
+                }
             }
         }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AccessTokenServiceImpl.java
----------------------------------------------------------------------
diff --git 
a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AccessTokenServiceImpl.java
 
b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AccessTokenServiceImpl.java
new file mode 100644
index 0000000..2b19add
--- /dev/null
+++ 
b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AccessTokenServiceImpl.java
@@ -0,0 +1,73 @@
+/*
+ * 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.rest.cxf.service;
+
+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.RESTHeaders;
+import org.apache.syncope.common.rest.api.beans.AccessTokenQuery;
+import org.springframework.stereotype.Service;
+import org.apache.syncope.common.rest.api.service.AccessTokenService;
+import org.apache.syncope.core.logic.AccessTokenLogic;
+import org.springframework.beans.factory.annotation.Autowired;
+
+@Service
+public class AccessTokenServiceImpl extends AbstractServiceImpl implements 
AccessTokenService {
+
+    @Autowired
+    private AccessTokenLogic logic;
+
+    @Override
+    public Response login() {
+        return Response.noContent().
+                header(RESTHeaders.TOKEN, 
logic.login(messageContext.getHttpServletRequest().getRemoteHost())).
+                build();
+    }
+
+    @Override
+    public Response refresh() {
+        return Response.noContent().
+                header(RESTHeaders.TOKEN, logic.refresh()).
+                build();
+    }
+
+    @Override
+    public void logout() {
+        logic.logout();
+    }
+
+    @Override
+    public PagedResult<AccessTokenTO> list(final AccessTokenQuery query) {
+        return buildPagedResult(
+                logic.list(
+                        query.getPage(),
+                        query.getSize(),
+                        getOrderByClauses(query.getOrderBy())),
+                query.getPage(),
+                query.getSize(),
+                logic.count());
+    }
+
+    @Override
+    public void delete(final String key) {
+        logic.delete(key);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/spring/pom.xml
----------------------------------------------------------------------
diff --git a/core/spring/pom.xml b/core/spring/pom.xml
index c79251e..d92d4e0 100644
--- a/core/spring/pom.xml
+++ b/core/spring/pom.xml
@@ -55,6 +55,11 @@ under the License.
     </dependency>
     
     <dependency>
+      <groupId>org.apache.cxf</groupId>
+      <artifactId>cxf-rt-rs-security-jose</artifactId>
+    </dependency>
+
+    <dependency>
       <groupId>org.springframework.security</groupId>
       <artifactId>spring-security-core</artifactId>
     </dependency>

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/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 e8a919d..af85985 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
@@ -35,9 +35,11 @@ import org.apache.commons.collections4.SetUtils;
 import org.apache.commons.collections4.Transformer;
 import org.apache.commons.lang3.tuple.ImmutablePair;
 import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.AuditElements;
 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.AnySearchDAO;
 import org.apache.syncope.core.provisioning.api.utils.RealmUtils;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
@@ -48,6 +50,7 @@ import org.apache.syncope.core.persistence.api.dao.RealmDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.entity.AccessToken;
 import org.apache.syncope.core.persistence.api.entity.Domain;
 import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.Role;
@@ -57,14 +60,17 @@ import 
org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.provisioning.api.AuditManager;
 import org.apache.syncope.core.provisioning.api.ConnectorFactory;
+import org.apache.syncope.core.provisioning.api.EntitlementsHolder;
 import org.apache.syncope.core.provisioning.api.MappingManager;
 import org.identityconnectors.framework.common.objects.Uid;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import 
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
 import 
org.springframework.security.authentication.AuthenticationServiceException;
 import org.springframework.security.authentication.DisabledException;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
 import org.springframework.transaction.annotation.Transactional;
 
 /**
@@ -79,6 +85,9 @@ public class AuthDataAccessor {
 
     protected static final Encryptor ENCRYPTOR = Encryptor.getInstance();
 
+    protected static final Set<SyncopeGrantedAuthority> ANONYMOUS_AUTHORITIES =
+            Collections.singleton(new 
SyncopeGrantedAuthority(StandardEntitlement.ANONYMOUS));
+
     @Resource(name = "adminUser")
     protected String adminUser;
 
@@ -107,6 +116,9 @@ public class AuthDataAccessor {
     protected AnySearchDAO searchDAO;
 
     @Autowired
+    protected AccessTokenDAO accessTokenDAO;
+
+    @Autowired
     protected ConnectorFactory connFactory;
 
     @Autowired
@@ -170,7 +182,7 @@ public class AuthDataAccessor {
             }
 
             boolean userModified = false;
-            authenticated = authenticate(user, 
authentication.getCredentials().toString());
+            authenticated = AuthDataAccessor.this.authenticate(user, 
authentication.getCredentials().toString());
             if (authenticated) {
                 if (confDAO.find("log.lastlogindate", 
Boolean.toString(true)).getValues().get(0).getBooleanValue()) {
                     user.setLastLoginDate(new Date());
@@ -249,23 +261,20 @@ public class AuthDataAccessor {
         return SetUtils.emptyIfNull(result);
     }
 
-    @Transactional(readOnly = true)
-    public void audit(
-            final AuditElements.EventCategoryType type,
-            final String category,
-            final String subcategory,
-            final String event,
-            final AuditElements.Result result,
-            final Object before,
-            final Object output,
-            final Object... input) {
+    protected Set<SyncopeGrantedAuthority> getAdminAuthorities() {
+        return 
CollectionUtils.collect(EntitlementsHolder.getInstance().getValues(),
+                new Transformer<String, SyncopeGrantedAuthority>() {
 
-        auditManager.audit(type, category, subcategory, event, result, before, 
output, input);
+            @Override
+            public SyncopeGrantedAuthority transform(final String entitlement) 
{
+                return new SyncopeGrantedAuthority(entitlement, 
SyncopeConstants.ROOT_REALM);
+            }
+        }, new HashSet<SyncopeGrantedAuthority>());
     }
 
-    @Transactional
-    public Set<SyncopeGrantedAuthority> getAuthorities(final User user) {
+    protected Set<SyncopeGrantedAuthority> getUserAuthorities(final User user) 
{
         Set<SyncopeGrantedAuthority> authorities = new HashSet<>();
+
         if (user.isMustChangePassword()) {
             authorities.add(new 
SyncopeGrantedAuthority(StandardEntitlement.MUST_CHANGE_PASSWORD));
         } else {
@@ -322,4 +331,73 @@ public class AuthDataAccessor {
 
         return authorities;
     }
+
+    @Transactional
+    public Set<SyncopeGrantedAuthority> getAuthorities(final String username) {
+        Set<SyncopeGrantedAuthority> authorities;
+
+        if (anonymousUser.equals(username)) {
+            authorities = ANONYMOUS_AUTHORITIES;
+        } else if (adminUser.equals(username)) {
+            authorities = getAdminAuthorities();
+        } else {
+            User user = userDAO.findByUsername(username);
+            if (user == null) {
+                throw new UsernameNotFoundException("Could not find any user 
with id " + username);
+            }
+
+            authorities = getUserAuthorities(user);
+        }
+
+        return authorities;
+    }
+
+    @Transactional
+    public Set<SyncopeGrantedAuthority> authenticate(final JWTAuthentication 
authentication) {
+        AccessToken accessToken = 
accessTokenDAO.find(authentication.getClaims().getTokenId());
+        if (accessToken == null) {
+            throw new AuthenticationCredentialsNotFoundException(
+                    "Could not find JWT " + 
authentication.getClaims().getTokenId());
+        }
+
+        Set<SyncopeGrantedAuthority> authorities;
+        if (adminUser.equals(accessToken.getOwner())) {
+            authorities = getAdminAuthorities();
+        } else {
+            User user = userDAO.findByUsername(accessToken.getOwner());
+            if (user == null) {
+                throw new AuthenticationCredentialsNotFoundException(
+                        "Could not find user " + accessToken.getOwner()
+                        + " for JWT " + 
authentication.getClaims().getTokenId());
+            }
+
+            if (user.isSuspended() != null && user.isSuspended()) {
+                throw new DisabledException("User " + user.getUsername() + " 
is suspended");
+            }
+
+            CPlainAttr authStatuses = confDAO.find("authentication.statuses");
+            if (authStatuses != null && 
!authStatuses.getValuesAsStrings().contains(user.getStatus())) {
+                throw new DisabledException("User " + user.getUsername() + " 
not allowed to authenticate");
+            }
+
+            authorities = getUserAuthorities(user);
+        }
+
+        return authorities;
+    }
+
+    @Transactional(readOnly = true)
+    public void audit(
+            final AuditElements.EventCategoryType type,
+            final String category,
+            final String subcategory,
+            final String event,
+            final AuditElements.Result result,
+            final Object before,
+            final Object output,
+            final Object... input) {
+
+        auditManager.audit(type, category, subcategory, event, result, before, 
output, input);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthentication.java
----------------------------------------------------------------------
diff --git 
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthentication.java
 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthentication.java
new file mode 100644
index 0000000..7348938
--- /dev/null
+++ 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthentication.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.core.spring.security;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
+import org.springframework.security.core.Authentication;
+
+/**
+ * Represents the token for an authentication request or for an authenticated 
principal as JSON Web Token,
+ * once the request has been processed by the
+ * {@link 
org.springframework.security.authentication.AuthenticationManager#authenticate(Authentication)}
 method.
+ */
+public class JWTAuthentication implements Authentication {
+
+    private static final long serialVersionUID = -2013733709281305394L;
+
+    private final JwtClaims claims;
+
+    private final SyncopeAuthenticationDetails details;
+
+    private final Set<SyncopeGrantedAuthority> authorities = new HashSet<>();
+
+    private boolean authenticated = false;
+
+    public JWTAuthentication(final JwtClaims claims, final 
SyncopeAuthenticationDetails details) {
+        this.claims = claims;
+        this.details = details;
+    }
+
+    public JwtClaims getClaims() {
+        return claims;
+    }
+
+    @Override
+    public Collection<SyncopeGrantedAuthority> getAuthorities() {
+        return authorities;
+    }
+
+    @Override
+    public Object getCredentials() {
+        return StringUtils.EMPTY;
+    }
+
+    @Override
+    public SyncopeAuthenticationDetails getDetails() {
+        return details;
+    }
+
+    @Override
+    public Object getPrincipal() {
+        return claims.getSubject();
+    }
+
+    @Override
+    public boolean isAuthenticated() {
+        return authenticated;
+    }
+
+    @Override
+    public void setAuthenticated(final boolean authenticated) throws 
IllegalArgumentException {
+        this.authenticated = authenticated;
+    }
+
+    @Override
+    public String getName() {
+        return claims.getSubject();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/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
new file mode 100644
index 0000000..c1520fa
--- /dev/null
+++ 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationFilter.java
@@ -0,0 +1,108 @@
+/*
+ * 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 java.io.IOException;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactConsumer;
+import org.apache.cxf.rs.security.jose.jws.JwsSignatureVerifier;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.util.Assert;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+/**
+ * Processes the JSON Web Token provided as {@link RESTHeaders#TOKEN} HTTP 
header, putting the result into the
+ * {@link SecurityContextHolder}.
+ */
+public class JWTAuthenticationFilter extends OncePerRequestFilter {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(JWTAuthenticationFilter.class);
+
+    private AuthenticationEntryPoint authenticationEntryPoint;
+
+    private AuthenticationManager authenticationManager;
+
+    private SyncopeAuthenticationDetailsSource authenticationDetailsSource;
+
+    @Autowired
+    private JwsSignatureVerifier jwsSignatureCerifier;
+
+    public void setAuthenticationEntryPoint(final AuthenticationEntryPoint 
authenticationEntryPoint) {
+        this.authenticationEntryPoint = authenticationEntryPoint;
+    }
+
+    public void setAuthenticationManager(final AuthenticationManager 
authenticationManager) {
+        this.authenticationManager = authenticationManager;
+    }
+
+    public void setAuthenticationDetailsSource(final 
SyncopeAuthenticationDetailsSource authenticationDetailsSource) {
+        this.authenticationDetailsSource = authenticationDetailsSource;
+    }
+
+    @Override
+    public void afterPropertiesSet() {
+        Assert.notNull(this.authenticationEntryPoint, "An 
AuthenticationEntryPoint is required");
+        Assert.notNull(this.authenticationManager, "An AuthenticationManager 
is required");
+        Assert.notNull(this.authenticationDetailsSource, 
"AuthenticationDetailsSource required");
+    }
+
+    @Override
+    protected void doFilterInternal(
+            final HttpServletRequest request,
+            final HttpServletResponse response,
+            final FilterChain chain)
+            throws ServletException, IOException {
+
+        String stringToken = request.getHeader(RESTHeaders.TOKEN);
+        if (stringToken == null) {
+            chain.doFilter(request, response);
+            return;
+        }
+
+        LOG.debug("JWT receveid: {}", stringToken);
+
+        JwsJwtCompactConsumer consumer = new 
JwsJwtCompactConsumer(stringToken);
+        try {
+            if (!consumer.verifySignatureWith(jwsSignatureCerifier)) {
+                throw new BadCredentialsException("Invalid signature found in 
JWT");
+            }
+
+            Authentication authentication = authenticationManager.authenticate(
+                    new JWTAuthentication(consumer.getJwtClaims(), 
authenticationDetailsSource.buildDetails(request)));
+            
SecurityContextHolder.getContext().setAuthentication(authentication);
+
+            chain.doFilter(request, response);
+        } catch (AuthenticationException e) {
+            SecurityContextHolder.clearContext();
+            this.authenticationEntryPoint.commence(request, response, e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/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
new file mode 100644
index 0000000..9686fd7
--- /dev/null
+++ 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationProvider.java
@@ -0,0 +1,91 @@
+/*
+ * 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 java.util.Date;
+import javax.annotation.Resource;
+import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
+import org.apache.syncope.common.lib.SyncopeConstants;
+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;
+
+/**
+ * Attempts to authenticate the passed {@link JWTAuthentication} object, 
returning a fully populated
+ * {@link Authentication} object (including granted authorities) if successful.
+ */
+public class JWTAuthenticationProvider implements AuthenticationProvider {
+
+    @Resource(name = "jwtIssuer")
+    private String jwtIssuer;
+
+    @Autowired
+    private AuthDataAccessor dataAccessor;
+
+    @Override
+    public Authentication authenticate(final Authentication authentication) 
throws AuthenticationException {
+        final JWTAuthentication jwtAuthentication = (JWTAuthentication) 
authentication;
+
+        AuthContextUtils.execWithAuthContext(
+                jwtAuthentication.getDetails().getDomain(),
+                new AuthContextUtils.Executable<Void>() {
+
+            @Override
+            public Void exec() {
+                
jwtAuthentication.getAuthorities().addAll(dataAccessor.authenticate(jwtAuthentication));
+                return null;
+            }
+        });
+
+        JwtClaims claims = jwtAuthentication.getClaims();
+        Long referenceTime = new Date().getTime();
+
+        Long expiryTime = claims.getExpiryTime();
+        if (expiryTime == null || expiryTime < referenceTime) {
+            throw new CredentialsExpiredException("JWT is expired");
+        }
+
+        Long notBefore = claims.getNotBefore();
+        if (notBefore == null || notBefore > referenceTime) {
+            throw new CredentialsExpiredException("JWT not valid yet");
+        }
+
+        if (!jwtIssuer.equals(claims.getIssuer())) {
+            throw new BadCredentialsException("Invalid JWT issuer");
+        }
+
+        if (!claims.containsProperty(SyncopeConstants.JWT_CLAIM_REMOTE_HOST)
+                || !claims.getClaim(SyncopeConstants.JWT_CLAIM_REMOTE_HOST).
+                        
equals(jwtAuthentication.getDetails().getRemoteHost())) {
+
+            throw new BadCredentialsException("Unexpected property found in 
JWT");
+        }
+
+        jwtAuthentication.setAuthenticated(true);
+        return jwtAuthentication;
+    }
+
+    @Override
+    public boolean supports(final Class<?> authentication) {
+        return JWTAuthentication.class.isAssignableFrom(authentication);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetails.java
----------------------------------------------------------------------
diff --git 
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetails.java
 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetails.java
index c427c48..d2080aa 100644
--- 
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetails.java
+++ 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetails.java
@@ -20,52 +20,40 @@ package org.apache.syncope.core.spring.security;
 
 import java.io.Serializable;
 import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpSession;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
 import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
+import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 
 public class SyncopeAuthenticationDetails implements Serializable {
 
     private static final long serialVersionUID = -5899959397393502897L;
 
-    private final String remoteAddress;
+    private final String remoteHost;
 
-    private final String sessionId;
-
-    private String domain;
+    private final String domain;
 
     public SyncopeAuthenticationDetails(final HttpServletRequest request) {
-        this.remoteAddress = request.getRemoteAddr();
-
-        HttpSession session = request.getSession(false);
-        this.sessionId = session == null ? null : session.getId();
-
         this.domain = request.getHeader(RESTHeaders.DOMAIN);
+        this.remoteHost = request.getRemoteHost();
     }
 
     public SyncopeAuthenticationDetails(final String domain) {
-        this.remoteAddress = null;
-        this.sessionId = null;
         this.domain = domain;
-    }
-
-    public String getRemoteAddress() {
-        return remoteAddress;
-    }
-
-    public String getSessionId() {
-        return sessionId;
+        this.remoteHost = null;
     }
 
     public String getDomain() {
-        return domain;
+        return StringUtils.isBlank(domain)
+                ? SyncopeConstants.MASTER_DOMAIN
+                : domain;
     }
 
-    public void setDomain(final String domain) {
-        this.domain = domain;
+    public String getRemoteHost() {
+        return remoteHost;
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationProvider.java
----------------------------------------------------------------------
diff --git 
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationProvider.java
 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationProvider.java
deleted file mode 100644
index 9f6a3ed..0000000
--- 
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationProvider.java
+++ /dev/null
@@ -1,235 +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.core.spring.security;
-
-import java.util.HashSet;
-import java.util.Set;
-import javax.annotation.Resource;
-import org.apache.commons.collections4.CollectionUtils;
-import org.apache.commons.collections4.Transformer;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.tuple.Pair;
-import org.apache.syncope.common.lib.SyncopeConstants;
-import org.apache.syncope.common.lib.types.AuditElements;
-import org.apache.syncope.common.lib.types.AuditElements.Result;
-import org.apache.syncope.common.lib.types.CipherAlgorithm;
-import org.apache.syncope.common.lib.types.StandardEntitlement;
-import org.apache.syncope.core.spring.security.AuthContextUtils.Executable;
-import org.apache.syncope.core.persistence.api.entity.Domain;
-import org.apache.syncope.core.persistence.api.entity.user.User;
-import org.apache.syncope.core.provisioning.api.EntitlementsHolder;
-import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Configurable;
-import org.springframework.security.authentication.AuthenticationProvider;
-import org.springframework.security.authentication.BadCredentialsException;
-import 
org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.Authentication;
-
-@Configurable
-public class SyncopeAuthenticationProvider implements AuthenticationProvider {
-
-    protected static final Logger LOG = 
LoggerFactory.getLogger(SyncopeAuthenticationProvider.class);
-
-    @Autowired
-    protected AuthDataAccessor dataAccessor;
-
-    @Autowired
-    protected UserProvisioningManager provisioningManager;
-
-    @Resource(name = "adminUser")
-    protected String adminUser;
-
-    @Resource(name = "anonymousUser")
-    protected String anonymousUser;
-
-    protected String adminPassword;
-
-    protected String adminPasswordAlgorithm;
-
-    protected String anonymousKey;
-
-    protected final Encryptor encryptor = Encryptor.getInstance();
-
-    /**
-     * @param adminPassword the adminPassword to set
-     */
-    public void setAdminPassword(final String adminPassword) {
-        this.adminPassword = adminPassword;
-    }
-
-    /**
-     * @param adminPasswordAlgorithm the adminPasswordAlgorithm to set
-     */
-    public void setAdminPasswordAlgorithm(final String adminPasswordAlgorithm) 
{
-        this.adminPasswordAlgorithm = adminPasswordAlgorithm;
-    }
-
-    /**
-     * @param anonymousKey the anonymousKey to set
-     */
-    public void setAnonymousKey(final String anonymousKey) {
-        this.anonymousKey = anonymousKey;
-    }
-
-    @Override
-    public Authentication authenticate(final Authentication authentication) {
-        String domainKey = 
SyncopeAuthenticationDetails.class.cast(authentication.getDetails()).getDomain();
-        if (StringUtils.isBlank(domainKey)) {
-            domainKey = SyncopeConstants.MASTER_DOMAIN;
-        }
-        
SyncopeAuthenticationDetails.class.cast(authentication.getDetails()).setDomain(domainKey);
-
-        final String[] username = new String[1];
-        Boolean authenticated;
-        final Set<SyncopeGrantedAuthority> authorities = new HashSet<>();
-
-        if (anonymousUser.equals(authentication.getName())) {
-            username[0] = anonymousUser;
-            authenticated = 
authentication.getCredentials().toString().equals(anonymousKey);
-            if (authenticated) {
-                authorities.add(new 
SyncopeGrantedAuthority(StandardEntitlement.ANONYMOUS));
-            }
-        } else if (adminUser.equals(authentication.getName())) {
-            username[0] = adminUser;
-            if (SyncopeConstants.MASTER_DOMAIN.equals(domainKey)) {
-                authenticated = encryptor.verify(
-                        authentication.getCredentials().toString(),
-                        CipherAlgorithm.valueOf(adminPasswordAlgorithm),
-                        adminPassword);
-            } else {
-                final String domainToFind = domainKey;
-                authenticated = AuthContextUtils.execWithAuthContext(
-                        SyncopeConstants.MASTER_DOMAIN, new 
Executable<Boolean>() {
-
-                    @Override
-                    public Boolean exec() {
-                        Domain domain = dataAccessor.findDomain(domainToFind);
-
-                        return encryptor.verify(
-                                authentication.getCredentials().toString(),
-                                domain.getAdminCipherAlgorithm(),
-                                domain.getAdminPwd());
-                    }
-                });
-            }
-            if (authenticated) {
-                CollectionUtils.collect(
-                        EntitlementsHolder.getInstance().getValues(),
-                        new Transformer<String, SyncopeGrantedAuthority>() {
-
-                    @Override
-                    public SyncopeGrantedAuthority transform(final String 
entitlement) {
-                        return new SyncopeGrantedAuthority(entitlement, 
SyncopeConstants.ROOT_REALM);
-                    }
-                }, authorities);
-            }
-        } else {
-            final Pair<User, Boolean> authResult =
-                    AuthContextUtils.execWithAuthContext(domainKey, new 
Executable<Pair<User, Boolean>>() {
-
-                        @Override
-                        public Pair<User, Boolean> exec() {
-                            return dataAccessor.authenticate(authentication);
-                        }
-                    });
-            authenticated = authResult.getValue();
-            if (authResult.getLeft() != null && authResult.getRight() != null) 
{
-                username[0] = authResult.getLeft().getUsername();
-
-                if (authResult.getRight()) {
-                    
authorities.addAll(dataAccessor.getAuthorities(authResult.getLeft()));
-                } else {
-                    AuthContextUtils.execWithAuthContext(domainKey, new 
Executable<Void>() {
-
-                        @Override
-                        public Void exec() {
-                            
provisioningManager.internalSuspend(authResult.getLeft().getKey());
-                            return null;
-                        }
-                    });
-                }
-            }
-        }
-        if (username[0] == null) {
-            username[0] = authentication.getPrincipal().toString();
-        }
-
-        final boolean isAuthenticated = authenticated != null && authenticated;
-        UsernamePasswordAuthenticationToken token;
-        if (isAuthenticated) {
-            token = AuthContextUtils.execWithAuthContext(
-                    domainKey, new 
Executable<UsernamePasswordAuthenticationToken>() {
-
-                @Override
-                public UsernamePasswordAuthenticationToken exec() {
-                    UsernamePasswordAuthenticationToken token = new 
UsernamePasswordAuthenticationToken(
-                            username[0],
-                            null,
-                            authorities);
-                    token.setDetails(authentication.getDetails());
-
-                    dataAccessor.audit(AuditElements.EventCategoryType.LOGIC,
-                            AuditElements.AUTHENTICATION_CATEGORY,
-                            null,
-                            AuditElements.LOGIN_EVENT,
-                            Result.SUCCESS,
-                            null,
-                            isAuthenticated,
-                            authentication,
-                            "Successfully authenticated, with entitlements: " 
+ token.getAuthorities());
-                    return token;
-                }
-            });
-
-            LOG.debug("User {} successfully authenticated, with entitlements 
{}",
-                    username[0], token.getAuthorities());
-        } else {
-            AuthContextUtils.execWithAuthContext(domainKey, new 
Executable<Void>() {
-
-                @Override
-                public Void exec() {
-                    dataAccessor.audit(AuditElements.EventCategoryType.LOGIC,
-                            AuditElements.AUTHENTICATION_CATEGORY,
-                            null,
-                            AuditElements.LOGIN_EVENT,
-                            Result.FAILURE,
-                            null,
-                            isAuthenticated,
-                            authentication,
-                            "User " + username[0] + " not authenticated");
-                    return null;
-                }
-            });
-
-            LOG.debug("User {} not authenticated", username[0]);
-
-            throw new BadCredentialsException("User " + username[0] + " not 
authenticated");
-        }
-
-        return token;
-    }
-
-    @Override
-    public boolean supports(final Class<? extends Object> type) {
-        return type.equals(UsernamePasswordAuthenticationToken.class);
-    }
-}

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeDigestAuthenticationEntryPoint.java
----------------------------------------------------------------------
diff --git 
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeDigestAuthenticationEntryPoint.java
 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeDigestAuthenticationEntryPoint.java
new file mode 100644
index 0000000..79b33c5
--- /dev/null
+++ 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeDigestAuthenticationEntryPoint.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.core.spring.security;
+
+import java.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.springframework.security.core.AuthenticationException;
+import 
org.springframework.security.web.authentication.www.DigestAuthenticationEntryPoint;
+
+/**
+ * Render Spring's {@link AuthenticationException} as other Syncope errors.
+ */
+public class SyncopeDigestAuthenticationEntryPoint extends 
DigestAuthenticationEntryPoint {
+
+    @Override
+    public void commence(final HttpServletRequest request, final 
HttpServletResponse response,
+            final AuthenticationException authException) throws IOException, 
ServletException {
+
+        response.addHeader(RESTHeaders.ERROR_INFO, authException.getMessage());
+
+        super.commence(request, response, authException);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeGrantedAuthority.java
----------------------------------------------------------------------
diff --git 
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeGrantedAuthority.java
 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeGrantedAuthority.java
index e23902b..578dd35 100644
--- 
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeGrantedAuthority.java
+++ 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeGrantedAuthority.java
@@ -18,6 +18,9 @@
  */
 package org.apache.syncope.core.spring.security;
 
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -36,11 +39,13 @@ public class SyncopeGrantedAuthority implements 
GrantedAuthority {
 
     private static final long serialVersionUID = -5647624636011919735L;
 
+    @JsonProperty
     private final String entitlement;
 
     private final Set<String> realms = SetUtils.orderedSet(new 
HashSet<String>());
 
-    public SyncopeGrantedAuthority(final String entitlement) {
+    @JsonCreator
+    public SyncopeGrantedAuthority(@JsonProperty("entitlement") final String 
entitlement) {
         this.entitlement = entitlement;
     }
 
@@ -67,6 +72,7 @@ public class SyncopeGrantedAuthority implements 
GrantedAuthority {
         return Collections.unmodifiableSet(realms);
     }
 
+    @JsonIgnore
     @Override
     public String getAuthority() {
         return entitlement;

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/spring/src/main/java/org/apache/syncope/core/spring/security/UsernamePasswordAuthenticationProvider.java
----------------------------------------------------------------------
diff --git 
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/UsernamePasswordAuthenticationProvider.java
 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/UsernamePasswordAuthenticationProvider.java
new file mode 100644
index 0000000..83a03c3
--- /dev/null
+++ 
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/UsernamePasswordAuthenticationProvider.java
@@ -0,0 +1,210 @@
+/*
+ * 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.commons.lang3.tuple.Pair;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.AuditElements.Result;
+import org.apache.syncope.common.lib.types.CipherAlgorithm;
+import org.apache.syncope.core.spring.security.AuthContextUtils.Executable;
+import org.apache.syncope.core.persistence.api.entity.Domain;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Configurable;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.BadCredentialsException;
+import 
org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+
+@Configurable
+public class UsernamePasswordAuthenticationProvider implements 
AuthenticationProvider {
+
+    protected static final Logger LOG = 
LoggerFactory.getLogger(UsernamePasswordAuthenticationProvider.class);
+
+    @Autowired
+    protected AuthDataAccessor dataAccessor;
+
+    @Autowired
+    protected UserProvisioningManager provisioningManager;
+
+    @Resource(name = "adminUser")
+    protected String adminUser;
+
+    @Resource(name = "adminPassword")
+    protected String adminPassword;
+
+    @Resource(name = "adminPasswordAlgorithm")
+    protected String adminPasswordAlgorithm;
+
+    @Resource(name = "anonymousUser")
+    protected String anonymousUser;
+
+    @Resource(name = "anonymousKey")
+    protected String anonymousKey;
+
+    protected final Encryptor encryptor = Encryptor.getInstance();
+
+    /**
+     * @param adminPassword the adminPassword to set
+     */
+    public void setAdminPassword(final String adminPassword) {
+        this.adminPassword = adminPassword;
+    }
+
+    /**
+     * @param adminPasswordAlgorithm the adminPasswordAlgorithm to set
+     */
+    public void setAdminPasswordAlgorithm(final String adminPasswordAlgorithm) 
{
+        this.adminPasswordAlgorithm = adminPasswordAlgorithm;
+    }
+
+    /**
+     * @param anonymousKey the anonymousKey to set
+     */
+    public void setAnonymousKey(final String anonymousKey) {
+        this.anonymousKey = anonymousKey;
+    }
+
+    @Override
+    public Authentication authenticate(final Authentication authentication) {
+        String domainKey = 
SyncopeAuthenticationDetails.class.cast(authentication.getDetails()).getDomain();
+
+        final String[] username = new String[1];
+        Boolean authenticated;
+
+        if (anonymousUser.equals(authentication.getName())) {
+            username[0] = anonymousUser;
+            authenticated = 
authentication.getCredentials().toString().equals(anonymousKey);
+        } else if (adminUser.equals(authentication.getName())) {
+            username[0] = adminUser;
+            if (SyncopeConstants.MASTER_DOMAIN.equals(domainKey)) {
+                authenticated = encryptor.verify(
+                        authentication.getCredentials().toString(),
+                        CipherAlgorithm.valueOf(adminPasswordAlgorithm),
+                        adminPassword);
+            } else {
+                final String domainToFind = domainKey;
+                authenticated = AuthContextUtils.execWithAuthContext(
+                        SyncopeConstants.MASTER_DOMAIN, new 
Executable<Boolean>() {
+
+                    @Override
+                    public Boolean exec() {
+                        Domain domain = dataAccessor.findDomain(domainToFind);
+
+                        return encryptor.verify(
+                                authentication.getCredentials().toString(),
+                                domain.getAdminCipherAlgorithm(),
+                                domain.getAdminPwd());
+                    }
+                });
+            }
+        } else {
+            final Pair<User, Boolean> authResult =
+                    AuthContextUtils.execWithAuthContext(domainKey, new 
Executable<Pair<User, Boolean>>() {
+
+                        @Override
+                        public Pair<User, Boolean> exec() {
+                            return dataAccessor.authenticate(authentication);
+                        }
+                    });
+            authenticated = authResult.getValue();
+            if (authResult.getLeft() != null && authResult.getRight() != null) 
{
+                username[0] = authResult.getLeft().getUsername();
+
+                if (!authResult.getRight()) {
+                    AuthContextUtils.execWithAuthContext(domainKey, new 
Executable<Void>() {
+
+                        @Override
+                        public Void exec() {
+                            
provisioningManager.internalSuspend(authResult.getLeft().getKey());
+                            return null;
+                        }
+                    });
+                }
+            }
+        }
+        if (username[0] == null) {
+            username[0] = authentication.getPrincipal().toString();
+        }
+
+        final boolean isAuthenticated = authenticated != null && authenticated;
+        UsernamePasswordAuthenticationToken token;
+        if (isAuthenticated) {
+            token = AuthContextUtils.execWithAuthContext(
+                    domainKey, new 
Executable<UsernamePasswordAuthenticationToken>() {
+
+                @Override
+                public UsernamePasswordAuthenticationToken exec() {
+                    UsernamePasswordAuthenticationToken token = new 
UsernamePasswordAuthenticationToken(
+                            username[0],
+                            null,
+                            dataAccessor.getAuthorities(username[0]));
+                    token.setDetails(authentication.getDetails());
+
+                    dataAccessor.audit(AuditElements.EventCategoryType.LOGIC,
+                            AuditElements.AUTHENTICATION_CATEGORY,
+                            null,
+                            AuditElements.LOGIN_EVENT,
+                            Result.SUCCESS,
+                            null,
+                            isAuthenticated,
+                            authentication,
+                            "Successfully authenticated, with entitlements: " 
+ token.getAuthorities());
+                    return token;
+                }
+            });
+
+            LOG.debug("User {} successfully authenticated, with entitlements 
{}",
+                    username[0], token.getAuthorities());
+        } else {
+            AuthContextUtils.execWithAuthContext(domainKey, new 
Executable<Void>() {
+
+                @Override
+                public Void exec() {
+                    dataAccessor.audit(AuditElements.EventCategoryType.LOGIC,
+                            AuditElements.AUTHENTICATION_CATEGORY,
+                            null,
+                            AuditElements.LOGIN_EVENT,
+                            Result.FAILURE,
+                            null,
+                            isAuthenticated,
+                            authentication,
+                            "User " + username[0] + " not authenticated");
+                    return null;
+                }
+            });
+
+            LOG.debug("User {} not authenticated", username[0]);
+
+            throw new BadCredentialsException("User " + username[0] + " not 
authenticated");
+        }
+
+        return token;
+    }
+
+    @Override
+    public boolean supports(final Class<? extends Object> type) {
+        return type.equals(UsernamePasswordAuthenticationToken.class);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/spring/src/main/resources/security.properties
----------------------------------------------------------------------
diff --git a/core/spring/src/main/resources/security.properties 
b/core/spring/src/main/resources/security.properties
index 8c85e21..d4f892b 100644
--- a/core/spring/src/main/resources/security.properties
+++ b/core/spring/src/main/resources/security.properties
@@ -22,6 +22,10 @@ anonymousUser=${anonymousUser}
 anonymousKey=${anonymousKey}
 
 secretKey=${secretKey}
+
+jwsKey=ZW7pRixehFuNUtnY5Se47IemgMryTzazPPJ9CGX5LTCmsOJpOgHAQEuPQeV9A28f
+jwtIssuer=ApacheSyncope
+
 # default for LDAP / RFC2307 SSHA
 digester.saltIterations=1
 digester.saltSizeBytes=8

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/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 3aa6079..809b78d 100644
--- a/core/spring/src/main/resources/securityContext.xml
+++ b/core/spring/src/main/resources/securityContext.xml
@@ -28,53 +28,89 @@ under the License.
   <bean id="adminUser" class="java.lang.String">
     <constructor-arg value="${adminUser}"/>
   </bean>
+  <bean id="adminPassword" class="java.lang.String">
+    <constructor-arg value="${adminPassword}"/>
+  </bean>
+  <bean id="adminPasswordAlgorithm" class="java.lang.String">
+    <constructor-arg value="${adminPasswordAlgorithm}"/>
+  </bean>
+
   <bean id="anonymousUser" class="java.lang.String">
     <constructor-arg value="${anonymousUser}"/>
   </bean>
+  <bean id="anonymousKey" class="java.lang.String">
+    <constructor-arg value="${anonymousKey}"/>
+  </bean>
+  
+  <bean id="jwtIssuer" class="java.lang.String">
+    <constructor-arg value="${jwtIssuer}"/>
+  </bean>
+  <bean id="jwsKey" class="java.lang.String">
+    <constructor-arg value="${jwsKey}"/>
+  </bean>
+  <bean id="jwsSignatureVerifier" 
class="org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureVerifier">
+    <constructor-arg value="${jwsKey}.bytes" 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}.bytes" index="0"/>
+    <constructor-arg index="1">
+      <value 
type="org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm">HS512</value>
+    </constructor-arg>
+  </bean>
   
   <bean class="${passwordGenerator}"/>
   <bean 
class="org.apache.syncope.core.spring.DefaultRolesPrefixPostProcessor"/>
   
   <security:global-method-security pre-post-annotations="enabled"/>
   
+  <bean id="securityContextRepository" 
class='org.springframework.security.web.context.NullSecurityContextRepository'/>
+  <bean id="securityContextPersistenceFilter"
+        
class="org.springframework.security.web.context.SecurityContextPersistenceFilter">
+    <constructor-arg ref="securityContextRepository"/>
+  </bean>
+
   <bean id="filterChainProxy" 
class="org.springframework.security.web.FilterChainProxy">
     <security:filter-chain-map request-matcher="ant">
       <security:filter-chain pattern="/**" 
filters="securityContextPersistenceFilter"/>
     </security:filter-chain-map>
-  </bean>
-  
-  <bean id="securityContextRepository" 
class='org.springframework.security.web.context.NullSecurityContextRepository'/>
+  </bean>  
 
-  <bean id="securityContextPersistenceFilter"
-        
class="org.springframework.security.web.context.SecurityContextPersistenceFilter">
-    <constructor-arg ref="securityContextRepository"/>
+  <bean id="firewall" 
class="org.springframework.security.web.firewall.DefaultHttpFirewall">
+    <property name="allowUrlEncodedSlash" value="true"/>
   </bean>
+  <security:http-firewall ref="firewall"/>
 
-  <bean id="syncopeAuthenticationDetailsSource"
+  <bean id="authenticationDetailsSource"
         
class="org.apache.syncope.core.spring.security.SyncopeAuthenticationDetailsSource"/>
-
-  <bean id="mustChangePasswordFilter" 
class="org.apache.syncope.core.spring.security.MustChangePasswordFilter"/>
       
   <bean id="basicAuthenticationEntryPoint" 
         
class="org.apache.syncope.core.spring.security.SyncopeBasicAuthenticationEntryPoint">
     <property name="realmName" value="Apache Syncope authentication"/>
   </bean>
   
-  <bean id="syncopeAccessDeniedHandler" 
class="org.apache.syncope.core.spring.security.SyncopeAccessDeniedHandler"/>
-  
-  <bean id="firewall" 
class="org.springframework.security.web.firewall.DefaultHttpFirewall">
-    <property name="allowUrlEncodedSlash" value="true"/>
+  <bean id="jwtAuthenticationFilter" 
class="org.apache.syncope.core.spring.security.JWTAuthenticationFilter">
+    <property name="authenticationManager" ref="authenticationManager"/>
+    <property name="authenticationEntryPoint" 
ref="basicAuthenticationEntryPoint"/>
+    <property name="authenticationDetailsSource" 
ref="authenticationDetailsSource"/>
   </bean>
-  <security:http-firewall ref="firewall"/>
+  
+  <bean id="mustChangePasswordFilter" 
class="org.apache.syncope.core.spring.security.MustChangePasswordFilter"/>
 
-  <security:http security-context-repository-ref="securityContextRepository"
+  <bean id="syncopeAccessDeniedHandler" 
class="org.apache.syncope.core.spring.security.SyncopeAccessDeniedHandler"/>
+    
+  <security:http create-session="stateless"
+                 security-context-repository-ref="securityContextRepository"
                  entry-point-ref="basicAuthenticationEntryPoint"
-                 use-expressions="false" disable-url-rewriting="false">
+                 use-expressions="false" disable-url-rewriting="false"
+                 pattern="/**">
 
-    <security:http-basic entry-point-ref="basicAuthenticationEntryPoint"
-                         
authentication-details-source-ref="syncopeAuthenticationDetailsSource"/>
     <security:anonymous username="${anonymousUser}"/>
-    <security:intercept-url pattern="/**"/>
+
+    <security:custom-filter ref="jwtAuthenticationFilter" 
before="BASIC_AUTH_FILTER"/>
+    <security:http-basic 
authentication-details-source-ref="authenticationDetailsSource"/>
     
     <security:custom-filter before="FILTER_SECURITY_INTERCEPTOR" 
ref="mustChangePasswordFilter"/>
     
@@ -86,14 +122,14 @@ under the License.
 
   <bean class="org.apache.syncope.core.spring.security.AuthDataAccessor"/>
 
-  <bean id="syncopeAuthenticationProvider"
-        
class="org.apache.syncope.core.spring.security.SyncopeAuthenticationProvider">
-    <property name="adminPassword" value="${adminPassword}"/>
-    <property name="adminPasswordAlgorithm" value="${adminPasswordAlgorithm}"/>
-    <property name="anonymousKey" value="${anonymousKey}"/>
-  </bean>
+  <bean id="usernamePasswordAuthenticationProvider"
+        
class="org.apache.syncope.core.spring.security.UsernamePasswordAuthenticationProvider"/>
+
+  <bean id="jwtAuthenticationProvider"
+        
class="org.apache.syncope.core.spring.security.JWTAuthenticationProvider"/>
 
-  <security:authentication-manager>
-    <security:authentication-provider ref="syncopeAuthenticationProvider"/>
+  <security:authentication-manager alias="authenticationManager">
+    <security:authentication-provider 
ref="usernamePasswordAuthenticationProvider"/>
+    <security:authentication-provider ref="jwtAuthenticationProvider"/>
   </security:authentication-manager>
 </beans>

http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
----------------------------------------------------------------------
diff --git 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
index 4003c3b..282f8dc 100644
--- 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
+++ 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
@@ -52,6 +52,7 @@ import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.ConnConfProperty;
 import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.lib.types.SchemaType;
+import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.service.AnyObjectService;
 import org.apache.syncope.common.rest.api.service.AnyTypeClassService;
 import org.apache.syncope.common.rest.api.service.AnyTypeService;
@@ -284,7 +285,10 @@ public abstract class AbstractITCase {
         WebClient webClient = 
WebClient.fromClient(WebClient.client(adminClient.getService(serviceClass)));
         
webClient.accept(clientFactory.getContentType().getMediaType()).to(location.toASCIIString(),
 false);
 
-        return webClient.get(resultClass);
+        return webClient.
+                header(RESTHeaders.DOMAIN, adminClient.getDomain()).
+                header(RESTHeaders.TOKEN, adminClient.getJWT()).
+                get(resultClass);
     }
 
     @SuppressWarnings("unchecked")

Reply via email to