keycloak-aplcache

KEYCLOAK-378 KEYCLOAK-379 KEYCLOAK-381 Fix refresh token

3/15/2014 7:15:10 AM

Details

diff --git a/core/src/main/java/org/keycloak/representations/AccessToken.java b/core/src/main/java/org/keycloak/representations/AccessToken.java
index 8279214..262d07d 100755
--- a/core/src/main/java/org/keycloak/representations/AccessToken.java
+++ b/core/src/main/java/org/keycloak/representations/AccessToken.java
@@ -128,12 +128,12 @@ public class AccessToken extends IDToken {
     }
 
     @Override
-    public AccessToken expiration(long expiration) {
+    public AccessToken expiration(int expiration) {
         return (AccessToken) super.expiration(expiration);
     }
 
     @Override
-    public AccessToken notBefore(long notBefore) {
+    public AccessToken notBefore(int notBefore) {
         return (AccessToken) super.notBefore(notBefore);
     }
 
diff --git a/core/src/main/java/org/keycloak/representations/adapters/action/AdminAction.java b/core/src/main/java/org/keycloak/representations/adapters/action/AdminAction.java
index 7fcc3a0..1708594 100755
--- a/core/src/main/java/org/keycloak/representations/adapters/action/AdminAction.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/action/AdminAction.java
@@ -1,6 +1,7 @@
 package org.keycloak.representations.adapters.action;
 
 import org.codehaus.jackson.annotate.JsonIgnore;
+import org.keycloak.util.Time;
 
 /**
  * Posted to managed client from admin server.
@@ -34,8 +35,7 @@ public abstract class AdminAction {
 
     @JsonIgnore
     public boolean isExpired() {
-        long time = System.currentTimeMillis() / 1000;
-        return time > expiration;
+        return Time.currentTime() > expiration;
     }
 
     /**
diff --git a/core/src/main/java/org/keycloak/representations/JsonWebToken.java b/core/src/main/java/org/keycloak/representations/JsonWebToken.java
index 56ac51c..42e9328 100755
--- a/core/src/main/java/org/keycloak/representations/JsonWebToken.java
+++ b/core/src/main/java/org/keycloak/representations/JsonWebToken.java
@@ -2,6 +2,7 @@ package org.keycloak.representations;
 
 import org.codehaus.jackson.annotate.JsonIgnore;
 import org.codehaus.jackson.annotate.JsonProperty;
+import org.keycloak.util.Time;
 
 import java.io.Serializable;
 
@@ -13,9 +14,9 @@ public class JsonWebToken implements Serializable {
     @JsonProperty("jti")
     protected String id;
     @JsonProperty("exp")
-    protected long expiration;
+    protected int expiration;
     @JsonProperty("nbf")
-    protected long notBefore;
+    protected int notBefore;
     @JsonProperty("iat")
     protected int issuedAt;
     @JsonProperty("iss")
@@ -39,26 +40,25 @@ public class JsonWebToken implements Serializable {
     }
 
 
-    public long getExpiration() {
+    public int getExpiration() {
         return expiration;
     }
 
-    public JsonWebToken expiration(long expiration) {
+    public JsonWebToken expiration(int expiration) {
         this.expiration = expiration;
         return this;
     }
 
     @JsonIgnore
     public boolean isExpired() {
-        long time = System.currentTimeMillis() / 1000;
-        return time > expiration;
+        return Time.currentTime() > expiration;
     }
 
-    public long getNotBefore() {
+    public int getNotBefore() {
         return notBefore;
     }
 
-    public JsonWebToken notBefore(long notBefore) {
+    public JsonWebToken notBefore(int notBefore) {
         this.notBefore = notBefore;
         return this;
     }
@@ -66,7 +66,7 @@ public class JsonWebToken implements Serializable {
 
     @JsonIgnore
     public boolean isNotBefore() {
-        return (System.currentTimeMillis() / 1000) >= notBefore;
+        return Time.currentTime() >= notBefore;
 
     }
 
@@ -89,7 +89,7 @@ public class JsonWebToken implements Serializable {
      */
     @JsonIgnore
     public JsonWebToken issuedNow() {
-        issuedAt = (int)(System.currentTimeMillis() / 1000);
+        issuedAt = Time.currentTime();
         return this;
     }
 
diff --git a/core/src/main/java/org/keycloak/util/Time.java b/core/src/main/java/org/keycloak/util/Time.java
new file mode 100644
index 0000000..3b3aef4
--- /dev/null
+++ b/core/src/main/java/org/keycloak/util/Time.java
@@ -0,0 +1,12 @@
+package org.keycloak.util;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class Time {
+
+    public static int currentTime() {
+        return (int) (System.currentTimeMillis() / 1000);
+    }
+
+}
diff --git a/core/src/test/java/org/keycloak/RSAVerifierTest.java b/core/src/test/java/org/keycloak/RSAVerifierTest.java
index b9e2848..ac10bbf 100755
--- a/core/src/test/java/org/keycloak/RSAVerifierTest.java
+++ b/core/src/test/java/org/keycloak/RSAVerifierTest.java
@@ -9,6 +9,7 @@ import org.junit.BeforeClass;
 import org.junit.Test;
 import org.keycloak.jose.jws.JWSBuilder;
 import org.keycloak.representations.AccessToken;
+import org.keycloak.util.Time;
 
 import javax.security.auth.x500.X500Principal;
 import java.io.IOException;
@@ -145,7 +146,7 @@ public class RSAVerifierTest {
 
     @Test
     public void testNotBeforeGood() throws Exception {
-        token.notBefore((System.currentTimeMillis() / 1000) - 100);
+        token.notBefore(Time.currentTime() - 100);
 
         String encoded = new JWSBuilder()
                 .jsonContent(token)
@@ -161,7 +162,7 @@ public class RSAVerifierTest {
 
     @Test
     public void testNotBeforeBad() throws Exception {
-        token.notBefore((System.currentTimeMillis() / 1000) + 100);
+        token.notBefore(Time.currentTime() + 100);
 
         String encoded = new JWSBuilder()
                 .jsonContent(token)
@@ -178,7 +179,7 @@ public class RSAVerifierTest {
 
     @Test
     public void testExpirationGood() throws Exception {
-        token.expiration((System.currentTimeMillis() / 1000) + 100);
+        token.expiration(Time.currentTime() + 100);
 
         String encoded = new JWSBuilder()
                 .jsonContent(token)
@@ -194,7 +195,7 @@ public class RSAVerifierTest {
 
     @Test
     public void testExpirationBad() throws Exception {
-        token.expiration((System.currentTimeMillis() / 1000) - 100);
+        token.expiration(Time.currentTime() - 100);
 
         String encoded = new JWSBuilder()
                 .jsonContent(token)

pom.xml 8(+8 -0)

diff --git a/pom.xml b/pom.xml
index 5a629b7..00cdfeb 100755
--- a/pom.xml
+++ b/pom.xml
@@ -229,6 +229,13 @@
                 <groupId>junit</groupId>
                 <artifactId>junit</artifactId>
                 <version>4.11</version>
+                <scope>test</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.hamcrest</groupId>
+                <artifactId>hamcrest-all</artifactId>
+                <version>1.3</version>
+                <scope>test</scope>
             </dependency>
             <dependency>
                 <groupId>org.hibernate.javax.persistence</groupId>
@@ -310,6 +317,7 @@
 				<groupId>org.seleniumhq.selenium</groupId>
 				<artifactId>selenium-chrome-driver</artifactId>
 				<version>2.35.0</version>
+                <scope>test</scope>
 			</dependency>
             <dependency>
                 <groupId>org.mongodb</groupId>
diff --git a/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java b/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java
index 79da6b7..51ea351 100755
--- a/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java
+++ b/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java
@@ -6,6 +6,7 @@ import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserModel.RequiredAction;
 import org.keycloak.representations.AccessToken;
+import org.keycloak.util.Time;
 
 import javax.ws.rs.core.MultivaluedHashMap;
 import javax.ws.rs.core.MultivaluedMap;
@@ -25,7 +26,7 @@ public class AccessCodeEntry {
     protected String redirectUri;
     protected boolean rememberMe;
 
-    protected long expiration;
+    protected int expiration;
     protected RealmModel realm;
     protected AccessToken token;
     protected UserModel user;
@@ -35,7 +36,7 @@ public class AccessCodeEntry {
     MultivaluedMap<String, RoleModel> resourceRolesRequested = new MultivaluedHashMap<String, RoleModel>();
 
     public boolean isExpired() {
-        return expiration != 0 && (System.currentTimeMillis() / 1000) > expiration;
+        return expiration != 0 && Time.currentTime() > expiration;
     }
 
     public String getId() {
@@ -58,11 +59,11 @@ public class AccessCodeEntry {
         this.code = code;
     }
 
-    public long getExpiration() {
+    public int getExpiration() {
         return expiration;
     }
 
-    public void setExpiration(long expiration) {
+    public void setExpiration(int expiration) {
         this.expiration = expiration;
     }
 
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index b1a473a..e3c1e95 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -16,6 +16,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.resources.RealmsResource;
+import org.keycloak.util.Time;
 
 import javax.ws.rs.core.Cookie;
 import javax.ws.rs.core.HttpHeaders;
@@ -47,7 +48,7 @@ public class AuthenticationManager {
         token.subject(user.getId());
         token.audience(realm.getName());
         if (realm.getCentralLoginLifespan() > 0) {
-            token.expiration((System.currentTimeMillis() / 1000) + realm.getCentralLoginLifespan());
+            token.expiration(Time.currentTime() + realm.getCentralLoginLifespan());
         }
         return token;
     }
diff --git a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
index 3c0e741..24f3ee4 100755
--- a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
@@ -14,6 +14,7 @@ import org.keycloak.representations.adapters.action.SessionStats;
 import org.keycloak.representations.adapters.action.SessionStatsAction;
 import org.keycloak.representations.adapters.action.UserStats;
 import org.keycloak.representations.adapters.action.UserStatsAction;
+import org.keycloak.util.Time;
 
 import javax.ws.rs.client.Entity;
 import javax.ws.rs.core.Response;
@@ -44,7 +45,7 @@ public class ResourceAdminManager {
     public SessionStats getSessionStats(RealmModel realm, ApplicationModel application, boolean users, ResteasyClient client) {
         String managementUrl = application.getManagementUrl();
         if (managementUrl != null) {
-            SessionStatsAction adminAction = new SessionStatsAction(TokenIdGenerator.generateId(), (int)(System.currentTimeMillis() / 1000) + 30, application.getName());
+            SessionStatsAction adminAction = new SessionStatsAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, application.getName());
             adminAction.setListUsers(users);
             String token = new TokenManager().encodeToken(realm, adminAction);
             logger.info("session stats for application: {0} url: {1}", application.getName(), managementUrl);
@@ -91,7 +92,7 @@ public class ResourceAdminManager {
     public UserStats getUserStats(RealmModel realm, ApplicationModel application, UserModel user, ResteasyClient client) {
         String managementUrl = application.getManagementUrl();
         if (managementUrl != null) {
-            UserStatsAction adminAction = new UserStatsAction(TokenIdGenerator.generateId(), (int)(System.currentTimeMillis() / 1000) + 30, application.getName(), user.getId());
+            UserStatsAction adminAction = new UserStatsAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, application.getName(), user.getId());
             String token = new TokenManager().encodeToken(realm, adminAction);
             logger.info("session stats for application: {0} url: {1}", application.getName(), managementUrl);
             Response response = client.target(managementUrl).path(AdapterConstants.K_GET_USER_STATS).request().post(Entity.text(token));
@@ -130,7 +131,7 @@ public class ResourceAdminManager {
                 .build();
 
         try {
-            realm.setNotBefore((int)(System.currentTimeMillis()/1000));
+            realm.setNotBefore(Time.currentTime());
             List<ApplicationModel> resources = realm.getApplications();
             logger.debug("logging out {0} resources ", resources.size());
             for (ApplicationModel resource : resources) {
@@ -147,7 +148,7 @@ public class ResourceAdminManager {
                 .build();
 
         try {
-            resource.setNotBefore((int)(System.currentTimeMillis()/1000));
+            resource.setNotBefore(Time.currentTime());
             logoutApplication(realm, resource, user, client, resource.getNotBefore());
         } finally {
             client.close();
@@ -159,7 +160,7 @@ public class ResourceAdminManager {
     protected boolean logoutApplication(RealmModel realm, ApplicationModel resource, String user, ResteasyClient client, int notBefore) {
         String managementUrl = resource.getManagementUrl();
         if (managementUrl != null) {
-            LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), (int)(System.currentTimeMillis() / 1000) + 30, resource.getName(), user, notBefore);
+            LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getName(), user, notBefore);
             String token = new TokenManager().encodeToken(realm, adminAction);
             logger.info("logout user: {0} resource: {1} url: {2}", user, resource.getName(), managementUrl);
             Response response = client.target(managementUrl).path(AdapterConstants.K_LOGOUT).request().post(Entity.text(token));
@@ -204,7 +205,7 @@ public class ResourceAdminManager {
         if (notBefore <= 0) return false;
         String managementUrl = resource.getManagementUrl();
         if (managementUrl != null) {
-            PushNotBeforeAction adminAction = new PushNotBeforeAction(TokenIdGenerator.generateId(), (int)(System.currentTimeMillis() / 1000) + 30, resource.getName(), notBefore);
+            PushNotBeforeAction adminAction = new PushNotBeforeAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getName(), notBefore);
             String token = new TokenManager().encodeToken(realm, adminAction);
             logger.info("pushRevocation resource: {0} url: {1}", resource.getName(), managementUrl);
             Response response = client.target(managementUrl).path(AdapterConstants.K_PUSH_NOT_BEFORE).request().post(Entity.text(token));
diff --git a/services/src/main/java/org/keycloak/services/managers/TokenManager.java b/services/src/main/java/org/keycloak/services/managers/TokenManager.java
index beb916e..a9e6bcf 100755
--- a/services/src/main/java/org/keycloak/services/managers/TokenManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/TokenManager.java
@@ -16,6 +16,7 @@ import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.AccessTokenResponse;
 import org.keycloak.representations.IDToken;
 import org.keycloak.representations.RefreshToken;
+import org.keycloak.util.Time;
 
 import javax.ws.rs.core.MultivaluedHashMap;
 import javax.ws.rs.core.MultivaluedMap;
@@ -82,7 +83,7 @@ public class TokenManager {
 
         code.setToken(token);
         code.setRealm(realm);
-        code.setExpiration((System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan());
+        code.setExpiration(Time.currentTime() + realm.getAccessCodeLifespan());
         code.setClient(client);
         code.setUser(user);
         code.setState(state);
@@ -158,7 +159,7 @@ public class TokenManager {
                 if (app == null) {
                     throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "Application no longer exists", "Application no longer exists: " + app.getName());
                 }
-                for (String roleName : refreshToken.getRealmAccess().getRoles()) {
+                for (String roleName : entry.getValue().getRoles()) {
                     RoleModel role = app.getRole(roleName);
                     if (role == null) {
                         throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", "Unknown application role: " + roleName);
@@ -257,7 +258,7 @@ public class TokenManager {
         token.issuedFor(client.getLoginName());
         token.issuer(realm.getName());
         if (realm.getAccessTokenLifespan() > 0) {
-            token.expiration((System.currentTimeMillis() / 1000) + realm.getAccessTokenLifespan());
+            token.expiration(Time.currentTime() + realm.getAccessTokenLifespan());
         }
         initClaims(token, claimer, user);
         return token;
@@ -274,7 +275,7 @@ public class TokenManager {
         token.issuedFor(client.getClientId());
         token.issuer(realm.getName());
         if (realm.getAccessTokenLifespan() > 0) {
-            token.expiration((System.currentTimeMillis() / 1000) + realm.getAccessTokenLifespan());
+            token.expiration(Time.currentTime() + realm.getAccessTokenLifespan());
         }
         Set<String> allowedOrigins = client.getWebOrigins();
         if (allowedOrigins != null) {
@@ -356,7 +357,7 @@ public class TokenManager {
             refreshToken = new RefreshToken(accessToken);
             refreshToken.id(KeycloakModelUtils.generateId());
             refreshToken.issuedNow();
-            refreshToken.expiration((System.currentTimeMillis() / 1000) + realm.getRefreshTokenLifespan());
+            refreshToken.expiration(Time.currentTime() + realm.getRefreshTokenLifespan());
             return this;
         }
 
@@ -372,7 +373,7 @@ public class TokenManager {
             idToken.issuedFor(accessToken.getIssuedFor());
             idToken.issuer(accessToken.getIssuer());
             if (realm.getAccessTokenLifespan() > 0) {
-                idToken.expiration((System.currentTimeMillis() / 1000) + realm.getAccessTokenLifespan());
+                idToken.expiration(Time.currentTime() + realm.getAccessTokenLifespan());
             }
             idToken.setPreferredUsername(accessToken.getPreferredUsername());
             idToken.setGivenName(accessToken.getGivenName());
@@ -412,8 +413,7 @@ public class TokenManager {
                 res.setToken(encodedToken);
                 res.setTokenType("bearer");
                 if (accessToken.getExpiration() != 0) {
-                    long time = accessToken.getExpiration() - (System.currentTimeMillis() / 1000);
-                    res.setExpiresIn(time);
+                    res.setExpiresIn(accessToken.getExpiration() - Time.currentTime());
                 }
             }
             if (refreshToken != null) {
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index c548c78..038a9de 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -25,6 +25,7 @@ import org.keycloak.services.managers.ResourceAdminManager;
 import org.keycloak.services.managers.TokenManager;
 import org.keycloak.services.resources.flows.Flows;
 import org.keycloak.services.resources.flows.Urls;
+import org.keycloak.util.Time;
 
 import javax.ws.rs.BadRequestException;
 import javax.ws.rs.Consumes;
@@ -181,7 +182,7 @@ public class UsersResource {
             throw new NotFoundException();
         }
         // set notBefore so that user will be forced to log in.
-        user.setNotBefore((int) (System.currentTimeMillis() / 1000));
+        user.setNotBefore(Time.currentTime());
         new ResourceAdminManager().logoutUser(realm, user);
     }
 
@@ -514,7 +515,7 @@ public class UsersResource {
 
         AccessCodeEntry accessCode = tokenManager.createAccessCode(scope, state, redirect, realm, client, user);
         accessCode.setRequiredActions(requiredActions);
-        accessCode.setExpiration(System.currentTimeMillis() / 1000 + realm.getAccessCodeLifespanUserAction());
+        accessCode.setExpiration(Time.currentTime() + realm.getAccessCodeLifespanUserAction());
 
         try {
             new EmailSender(realm.getSmtpConfig()).sendPasswordReset(user, realm, accessCode, uriInfo);
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
index eeb6674..0a463e3 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
@@ -37,6 +37,7 @@ import org.keycloak.services.managers.AccessCodeEntry;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.TokenManager;
 import org.keycloak.services.resources.TokenService;
+import org.keycloak.util.Time;
 
 import javax.ws.rs.Path;
 import javax.ws.rs.core.Cookie;
@@ -127,14 +128,14 @@ public class OAuthFlows {
         Set<RequiredAction> requiredActions = user.getRequiredActions();
         if (!requiredActions.isEmpty()) {
             accessCode.setRequiredActions(new HashSet<UserModel.RequiredAction>(requiredActions));
-            accessCode.setExpiration(System.currentTimeMillis() / 1000 + realm.getAccessCodeLifespanUserAction());
+            accessCode.setExpiration(Time.currentTime() + realm.getAccessCodeLifespanUserAction());
             return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode.getId(), accessCode.getCode()).setUser(user)
                     .createResponse(user.getRequiredActions().iterator().next());
         }
 
         if (!isResource
                 && (accessCode.getRealmRolesRequested().size() > 0 || accessCode.getResourceRolesRequested().size() > 0)) {
-            accessCode.setExpiration(System.currentTimeMillis() / 1000 + realm.getAccessCodeLifespanUserAction());
+            accessCode.setExpiration(Time.currentTime() + realm.getAccessCodeLifespanUserAction());
             return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode.getId(), accessCode.getCode()).
                     setAccessRequest(accessCode.getRealmRolesRequested(), accessCode.getResourceRolesRequested()).
                     setClient(client).createOAuthGrant();
diff --git a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
index b8eb4db..5f5e252 100755
--- a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
@@ -41,6 +41,7 @@ import org.keycloak.services.managers.TokenManager;
 import org.keycloak.services.messages.Messages;
 import org.keycloak.services.resources.flows.Flows;
 import org.keycloak.services.validation.Validation;
+import org.keycloak.util.Time;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
@@ -268,7 +269,7 @@ public class RequiredActionsService {
 
             AccessCodeEntry accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, realm, client, user);
             accessCode.setRequiredActions(requiredActions);
-            accessCode.setExpiration(System.currentTimeMillis() / 1000 + realm.getAccessCodeLifespanUserAction());
+            accessCode.setExpiration(Time.currentTime() + realm.getAccessCodeLifespanUserAction());
 
             try {
                 new EmailSender(realm.getSmtpConfig()).sendPasswordReset(user, realm, accessCode, uriInfo);
@@ -312,7 +313,7 @@ public class RequiredActionsService {
         if (accessCodeEntry.isExpired()) {
             logger.debug("getAccessCodeEntry: access code id: {0}", accessCodeEntry.getId());
             logger.debug("getAccessCodeEntry access code entry expired: {0}", accessCodeEntry.getExpiration());
-            logger.debug("getAccessCodeEntry current time: {0}", (System.currentTimeMillis() / 1000));
+            logger.debug("getAccessCodeEntry current time: {0}", Time.currentTime());
             return null;
         }
 
@@ -339,7 +340,7 @@ public class RequiredActionsService {
                     .createResponse(requiredActions.iterator().next());
         } else {
             logger.debug("redirectOauth: redirecting to: {0}", accessCode.getRedirectUri());
-            accessCode.setExpiration((System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan());
+            accessCode.setExpiration(Time.currentTime() + realm.getAccessCodeLifespan());
             return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).redirectAccessCode(accessCode,
                     accessCode.getState(), accessCode.getRedirectUri());
         }
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index b9b1673..5a3b239 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -28,6 +28,7 @@ import org.keycloak.services.resources.flows.Flows;
 import org.keycloak.services.resources.flows.OAuthFlows;
 import org.keycloak.services.validation.Validation;
 import org.keycloak.util.BasicAuthHelper;
+import org.keycloak.util.Time;
 
 import javax.ws.rs.BadRequestException;
 import javax.ws.rs.Consumes;
@@ -622,7 +623,7 @@ public class TokenService {
             return redirectAccessDenied(redirect, state);
         }
 
-        accessCodeEntry.setExpiration((System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan());
+        accessCodeEntry.setExpiration(Time.currentTime() + realm.getAccessCodeLifespan());
         return oauth.redirectAccessCode(accessCodeEntry, state, redirect);
     }
 
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index ea61388..1591c7f 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -237,6 +237,10 @@
             <artifactId>junit</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-all</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.hibernate.javax.persistence</groupId>
             <artifactId>hibernate-jpa-2.0-api</artifactId>
         </dependency>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
index b894e2c..db57c43 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
@@ -25,10 +25,7 @@ import org.junit.Assert;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
-import org.keycloak.models.RealmModel;
 import org.keycloak.representations.AccessToken;
-import org.keycloak.representations.idm.UserRepresentation;
-import org.keycloak.services.managers.RealmManager;
 import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
 import org.keycloak.testsuite.pages.LoginPage;
@@ -37,6 +34,10 @@ import org.keycloak.testsuite.rule.WebResource;
 import org.keycloak.testsuite.rule.WebRule;
 import org.openqa.selenium.WebDriver;
 
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
@@ -66,7 +67,7 @@ public class AccessTokenTest {
 
         Assert.assertEquals(200, response.getStatusCode());
 
-        Assert.assertTrue(response.getExpiresIn() <= 600 && response.getExpiresIn() >= 550);
+        Assert.assertThat(response.getExpiresIn(), allOf(greaterThanOrEqualTo(250), lessThanOrEqualTo(300)));
 
         Assert.assertEquals("bearer", response.getTokenType());
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
new file mode 100755
index 0000000..8e70fb8
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
@@ -0,0 +1,110 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.oauth;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.RefreshToken;
+import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.keycloak.util.Time;
+import org.openqa.selenium.WebDriver;
+
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RefreshTokenTest {
+
+    @ClassRule
+    public static KeycloakRule keycloakRule = new KeycloakRule();
+
+    @Rule
+    public WebRule webRule = new WebRule(this);
+
+    @WebResource
+    protected WebDriver driver;
+
+    @WebResource
+    protected OAuthClient oauth;
+
+    @WebResource
+    protected LoginPage loginPage;
+
+    @Test
+    public void refreshTokenRequest() throws Exception {
+        oauth.doLogin("test-user@localhost", "password");
+
+        String code = oauth.getCurrentQuery().get("code");
+
+        AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
+        AccessToken token = oauth.verifyToken(tokenResponse.getAccessToken());
+        String refreshTokenString = tokenResponse.getRefreshToken();
+        RefreshToken refreshToken = oauth.verifyRefreshToken(refreshTokenString);
+
+        Assert.assertNotNull(refreshTokenString);
+
+        Assert.assertEquals("bearer", tokenResponse.getTokenType());
+
+        Assert.assertThat(token.getExpiration() - Time.currentTime(), allOf(greaterThanOrEqualTo(250), lessThanOrEqualTo(300)));
+        Assert.assertThat(refreshToken.getExpiration() - Time.currentTime(), allOf(greaterThanOrEqualTo(35950), lessThanOrEqualTo(36000)));
+
+        Thread.sleep(2000);
+
+        AccessTokenResponse response = oauth.doRefreshTokenRequest(refreshTokenString, "password");
+        AccessToken refreshedToken = oauth.verifyToken(response.getAccessToken());
+        RefreshToken refreshedRefreshToken = oauth.verifyRefreshToken(response.getRefreshToken());
+
+        Assert.assertEquals(200, response.getStatusCode());
+
+        Assert.assertThat(response.getExpiresIn(), allOf(greaterThanOrEqualTo(250), lessThanOrEqualTo(300)));
+        Assert.assertThat(refreshedToken.getExpiration() - Time.currentTime(), allOf(greaterThanOrEqualTo(250), lessThanOrEqualTo(300)));
+
+        Assert.assertThat(refreshedToken.getExpiration() - token.getExpiration(), allOf(greaterThanOrEqualTo(1), lessThanOrEqualTo(3)));
+        Assert.assertThat(refreshedRefreshToken.getExpiration() - refreshToken.getExpiration(), allOf(greaterThanOrEqualTo(1), lessThanOrEqualTo(3)));
+
+        Assert.assertNotEquals(token.getId(), refreshedToken.getId());
+        Assert.assertNotEquals(refreshToken.getId(), refreshedRefreshToken.getId());
+
+        Assert.assertEquals("bearer", response.getTokenType());
+
+        Assert.assertEquals(keycloakRule.getUser("test", "test-user@localhost").getId(), refreshedToken.getSubject());
+        Assert.assertNotEquals("test-user@localhost", refreshedToken.getSubject());
+
+        Assert.assertEquals(1, refreshedToken.getRealmAccess().getRoles().size());
+        Assert.assertTrue(refreshedToken.getRealmAccess().isUserInRole("user"));
+
+        Assert.assertEquals(1, refreshedToken.getResourceAccess(oauth.getClientId()).getRoles().size());
+        Assert.assertTrue(refreshedToken.getResourceAccess(oauth.getClientId()).isUserInRole("customer-user"));
+    }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
index 5b262bb..a449918 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
@@ -38,6 +38,7 @@ import org.keycloak.VerificationException;
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.jose.jws.crypto.RSAProvider;
 import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.RefreshToken;
 import org.keycloak.util.BasicAuthHelper;
 import org.openqa.selenium.By;
 import org.openqa.selenium.WebDriver;
@@ -141,6 +142,40 @@ public class OAuthClient {
         }
     }
 
+    public AccessTokenResponse doRefreshTokenRequest(String refreshToken, String password) {
+        HttpClient client = new DefaultHttpClient();
+        HttpPost post = new HttpPost(getRefreshTokenUrl());
+
+        List<NameValuePair> parameters = new LinkedList<NameValuePair>();
+        if (grantType != null) {
+            parameters.add(new BasicNameValuePair("grant_type", grantType));
+        }
+        if (refreshToken != null) {
+            parameters.add(new BasicNameValuePair("refresh_token", refreshToken));
+        }
+        if (clientId != null && password != null) {
+            String authorization = BasicAuthHelper.createHeader(clientId, password);
+            post.setHeader("Authorization", authorization);
+        }
+        else if (clientId != null) {
+            parameters.add(new BasicNameValuePair("client_id", clientId));
+        }
+
+        UrlEncodedFormEntity formEntity = null;
+        try {
+            formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+        post.setEntity(formEntity);
+
+        try {
+            return new AccessTokenResponse(client.execute(post));
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to retrieve access token", e);
+        }
+    }
+
     public AccessToken verifyToken(String token) {
         try {
             return RSATokenVerifier.verifyToken(token, realmPublicKey, realm);
@@ -155,6 +190,18 @@ public class OAuthClient {
         }
     }
 
+    public RefreshToken verifyRefreshToken(String refreshToken) {
+        try {
+            JWSInput jws = new JWSInput(refreshToken);
+            if (!RSAProvider.verify(jws, realmPublicKey)) {
+                throw new RuntimeException("Invalid refresh token");
+            }
+            return jws.readJsonContent(RefreshToken.class);
+        } catch (Exception e) {
+            throw new RuntimeException("Invalid refresh token", e);
+        }
+    }
+
     public String getClientId() {
         return clientId;
     }
@@ -218,6 +265,11 @@ public class OAuthClient {
         return b.build().toString();
     }
 
+    public String getRefreshTokenUrl() {
+        UriBuilder b = UriBuilder.fromUri(baseUrl + "/realms/" + realm + "/tokens/refresh");
+        return b.build().toString();
+    }
+
     public OAuthClient realm(String realm) {
         this.realm = realm;
         return this;
diff --git a/testsuite/integration/src/test/resources/testrealm.json b/testsuite/integration/src/test/resources/testrealm.json
index ed504aa..a7cf0d4 100755
--- a/testsuite/integration/src/test/resources/testrealm.json
+++ b/testsuite/integration/src/test/resources/testrealm.json
@@ -2,9 +2,6 @@
     "id": "test",
     "realm": "test",
     "enabled": true,
-    "accessTokenLifespan": 600,
-    "accessCodeLifespan": 600,
-    "accessCodeLifespanUserAction": 600,
     "sslNotRequired": true,
     "registrationAllowed": true,
     "resetPasswordAllowed": true,