Merge pull request #25 from patriot1burke/master

session timeout improvements
This commit is contained in:
Bill Burke 2013-08-08 06:53:56 -07:00
commit 25888b2ef4
7 changed files with 73 additions and 92 deletions

View file

@ -1,8 +1,8 @@
{ {
"realm" : "demo", "realm" : "demo",
"enabled" : true, "enabled" : true,
"tokenLifespan" : 6000, "tokenLifespan" : 10,
"accessCodeLifespan" : 30, "accessCodeLifespan" : 10,
"sslNotRequired" : true, "sslNotRequired" : true,
"cookieLoginAllowed" : true, "cookieLoginAllowed" : true,
"privateKey" : "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=", "privateKey" : "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",

View file

@ -22,7 +22,6 @@
<script src="lib/angular/angular-resource.js"></script> <script src="lib/angular/angular-resource.js"></script>
<script src="lib/angular/ui-bootstrap-tpls-0.4.0.js"></script> <script src="lib/angular/ui-bootstrap-tpls-0.4.0.js"></script>
<script src="/auth-server/rest/saas/isLoggedIn.js"></script>
<script src="js/app.js"></script> <script src="js/app.js"></script>
<script src="js/controllers.js"></script> <script src="js/controllers.js"></script>
<script src="js/loaders.js"></script> <script src="js/loaders.js"></script>
@ -63,13 +62,14 @@
<script src="lib/jquery/jquery.idletimeout.js" type="text/javascript"></script> <script src="lib/jquery/jquery.idletimeout.js" type="text/javascript"></script>
<script type="text/javascript"> <script type="text/javascript">
$.idleTimeout('#idletimeout', '#idletimeout a', { $.idleTimeout('#idletimeout', '#idletimeout a', {
idleAfter: 5, idleAfter: 300,
pollingInterval: 0, pollingInterval: 60,
keepAliveURL: '', keepAliveURL: '/auth-server/rest/saas/keepalive',
serverResponseEquals: '', serverResponseEquals: '',
failedRequests: 1,
onTimeout: function(){ onTimeout: function(){
$(this).slideUp(); $(this).slideUp();
window.location = "timeout.html"; window.location = "/auth-server/rest/saas/logout";
}, },
onIdle: function(){ onIdle: function(){
$(this).slideDown(); // show the warning bar $(this).slideDown(); // show the warning bar
@ -84,35 +84,4 @@
</script> </script>
</body> </body>
<!--
<body data-ng-controller="GlobalCtrl">
<div id="idletimeout">
You will be logged off in <span></span>&nbsp;seconds due to inactivity.
<a id="idletimeout-resume" href="#">Click here to continue using this web page</a>.
</div>
<div class="alert-container" data-ng-show="notification" data-ng-click="notification = null">
<div class="alert alert-{{notification.type}}">{{notification.message}}</div>
</div>
<div id="wrap">
<div data-ng-include data-src="'partials/menu.html'"></div>
<div data-ng-view id="view" data-ng-hide="httpProviderError"></div>
<div id="httpProviderError" data-ng-show="httpProviderError">
<button class="btn btn-danger" data-ng-click="httpProviderError=null">
<strong>Error</strong> {{httpProviderError}}
</button>
</div>
<div id="loading">
<i class="icon-spinner icon-spin"></i> Loading...
</div>
</div>
</body>
-->
</html> </html>

View file

@ -160,13 +160,19 @@ module.config(function($httpProvider) {
}); });
module.factory('errorInterceptor', function($q, $window, $rootScope, $location) { module.factory('errorInterceptor', function($q, $window, $rootScope, $location, Auth) {
return function(promise) { return function(promise) {
return promise.then(function(response) { return promise.then(function(response) {
$rootScope.httpProviderError = null; $rootScope.httpProviderError = null;
return response; return response;
}, function(response) { }, function(response) {
if (response.status == 401) {
console.log('session timeout?');
Auth.loggedIn = false;
$location.url('/');
} else {
$rootScope.httpProviderError = response.status; $rootScope.httpProviderError = response.status;
}
return $q.reject(response); return $q.reject(response);
}); });
}; };
@ -191,6 +197,7 @@ module.factory('spinnerInterceptor', function($q, $window, $rootScope, $location
}; };
}); });
/*
module.directive('kcInput', function() { module.directive('kcInput', function() {
var d = { var d = {
scope : true, scope : true,
@ -235,6 +242,7 @@ module.directive('kcEnter', function() {
}); });
}; };
}); });
*/
module.filter('remove', function() { module.filter('remove', function() {
return function(input, remove, attribute) { return function(input, remove, attribute) {

View file

@ -11,6 +11,13 @@ module.controller('GlobalCtrl', function($scope, $http, Auth, Current, $location
}; };
$scope.auth = Auth; $scope.auth = Auth;
$http.get('/auth-server/rest/saas/whoami').success(function(data, status) {
Auth.user = data;
Auth.loggedIn = true;
})
.error(function(data, status) {
Auth.loggedIn = false;
});
$scope.$watch(function() { $scope.$watch(function() {
return $location.path(); return $location.path();
@ -34,11 +41,11 @@ module.controller('GlobalCtrl', function($scope, $http, Auth, Current, $location
} }
if (showrealm) { if (showrealm) {
console.log('redirecting'); console.log('default redirect to realm: ' + id);
Current.realm = Current.realms[id]; Current.realm = Current.realms[id];
$location.url("/realms/" + id); $location.url("/realms/" + id);
} else { } else {
console.log('not redirecting'); //console.log('not redirecting');
} }
}); });
}); });

View file

@ -2,32 +2,14 @@
var module = angular.module('keycloak.services', [ 'ngResource' ]); var module = angular.module('keycloak.services', [ 'ngResource' ]);
module.service('Auth', function($resource, $http, $location, $routeParams) { module.service('Auth', function() {
var auth = { var auth = {
loggedIn : keycloakCookieLoggedIn loggedIn : false
}; };
auth.user = { auth.user = {
userId : null, userId : null,
displayName : null displayName : null
}; };
auth.logout = function() {
auth.user = {
userId : null,
displayName : null
};
auth.loggedIn = false;
$http.get('/auth-server/rest/saas/logout-cookie');
};
if (!auth.loggedIn) {
return auth;
}
$http.get('/auth-server/rest/saas/whoami').success(function(data, status) {
auth.user = data;
//alert(data.userId);
})
.error(function(data, status) {
alert("Failed!");
});
return auth; return auth;
}); });

View file

@ -82,9 +82,11 @@
_startTimer: function(){ _startTimer: function(){
var self = this; var self = this;
this.timer = win.setTimeout(function(){ if (this.options.pollingInterval > 0) {
this.timer = win.setTimeout(function () {
self._keepAlive(); self._keepAlive();
}, this.options.pollingInterval * 1000); }, this.options.pollingInterval * 1000);
}
}, },
_stopTimer: function(){ _stopTimer: function(){
@ -103,6 +105,7 @@
// if too many requests failed, abort // if too many requests failed, abort
if( !this.failedRequests ){ if( !this.failedRequests ){
console.log('aborting...');
this._stopTimer(); this._stopTimer();
options.onAbort.call( this.warning[0] ); options.onAbort.call( this.warning[0] );
return; return;
@ -112,12 +115,14 @@
timeout: options.AJAXTimeout, timeout: options.AJAXTimeout,
url: options.keepAliveURL, url: options.keepAliveURL,
error: function(){ error: function(){
console.log('failure for keepalive');
self.failedRequests--; self.failedRequests--;
}, },
success: function(response){ success: function(response){
if($.trim(response) !== options.serverResponseEquals){ console.log('success for keepalive');
/* if($.trim(response) !== options.serverResponseEquals){
self.failedRequests--; self.failedRequests--;
} }*/
}, },
complete: function(){ complete: function(){
if( recurse ){ if( recurse ){

View file

@ -1,5 +1,6 @@
package org.keycloak.services.resources; package org.keycloak.services.resources;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.logging.Logger; import org.jboss.resteasy.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.HttpResponse; import org.jboss.resteasy.spi.HttpResponse;
@ -11,27 +12,12 @@ import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.models.RealmModel; import org.keycloak.services.models.RealmModel;
import org.keycloak.services.models.RoleModel; import org.keycloak.services.models.RoleModel;
import org.keycloak.services.models.UserModel;
import org.keycloak.services.models.UserCredentialModel; import org.keycloak.services.models.UserCredentialModel;
import org.keycloak.services.models.UserModel;
import org.keycloak.services.resources.admin.RealmsAdminResource; import org.keycloak.services.resources.admin.RealmsAdminResource;
import javax.ws.rs.Consumes; import javax.ws.rs.*;
import javax.ws.rs.ForbiddenException; import javax.ws.rs.core.*;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.net.URI; import java.net.URI;
/** /**
@ -87,22 +73,42 @@ public class SaasService {
} }
} }
@Path("whoami") @Path("keepalive")
@GET @GET
@Produces("application/json") @NoCache
public Response whoAmI(final @Context HttpHeaders headers) { public Response keepalive(final @Context HttpHeaders headers) {
logger.info("keepalive");
return new Transaction() { return new Transaction() {
@Override @Override
public Response callImpl() { public Response callImpl() {
logger.info("WHOAMI start.");
RealmManager realmManager = new RealmManager(session); RealmManager realmManager = new RealmManager(session);
RealmModel realm = realmManager.defaultRealm(); RealmModel realm = realmManager.defaultRealm();
if (realm == null) throw new NotFoundException(); if (realm == null) throw new NotFoundException();
UserModel user = authManager.authenticateSaasIdentityCookie(realm, uriInfo, headers); UserModel user = authManager.authenticateSaasIdentityCookie(realm, uriInfo, headers);
if (user == null) { if (user == null) {
return Response.status(404).build(); return Response.status(401).build();
}
NewCookie refreshCookie = authManager.createSaasIdentityCookie(realm, user, uriInfo);
return Response.noContent().cookie(refreshCookie).build();
}
}.call();
}
@Path("whoami")
@GET
@Produces("application/json")
@NoCache
public Response whoAmI(final @Context HttpHeaders headers) {
return new Transaction() {
@Override
public Response callImpl() {
RealmManager realmManager = new RealmManager(session);
RealmModel realm = realmManager.defaultRealm();
if (realm == null) throw new NotFoundException();
UserModel user = authManager.authenticateSaasIdentityCookie(realm, uriInfo, headers);
if (user == null) {
return Response.status(401).build();
} }
logger.info("WHOAMI: " + user.getLoginName());
return Response.ok(new WhoAmI(user.getLoginName(), user.getLoginName())).build(); return Response.ok(new WhoAmI(user.getLoginName(), user.getLoginName())).build();
} }
}.call(); }.call();
@ -111,6 +117,7 @@ public class SaasService {
@Path("isLoggedIn.js") @Path("isLoggedIn.js")
@GET @GET
@Produces("application/javascript") @Produces("application/javascript")
@NoCache
public String isLoggedIn(final @Context HttpHeaders headers) { public String isLoggedIn(final @Context HttpHeaders headers) {
return new Transaction() { return new Transaction() {
@Override @Override
@ -165,6 +172,7 @@ public class SaasService {
@Path("loginPage.html") @Path("loginPage.html")
@GET @GET
@NoCache
public void loginPage() { public void loginPage() {
new Transaction() { new Transaction() {
@Override @Override
@ -179,6 +187,7 @@ public class SaasService {
@Path("logout") @Path("logout")
@GET @GET
@NoCache
public void logout() { public void logout() {
new Transaction() { new Transaction() {
@Override @Override
@ -194,6 +203,7 @@ public class SaasService {
@Path("logout-cookie") @Path("logout-cookie")
@GET @GET
@NoCache
public void logoutCookie() { public void logoutCookie() {
logger.info("*** logoutCookie"); logger.info("*** logoutCookie");
new Transaction() { new Transaction() {
@ -205,6 +215,7 @@ public class SaasService {
} }
public final static String loginFormPath = "/sdk/login.xhtml"; public final static String loginFormPath = "/sdk/login.xhtml";
protected void forwardToLoginForm(RealmModel realm) { protected void forwardToLoginForm(RealmModel realm) {
request.setAttribute(RealmModel.class.getName(), realm); request.setAttribute(RealmModel.class.getName(), realm);
URI action = uriInfo.getBaseUriBuilder().path(SaasService.class).path(SaasService.class, "processLogin").build(); URI action = uriInfo.getBaseUriBuilder().path(SaasService.class).path(SaasService.class, "processLogin").build();
@ -216,7 +227,6 @@ public class SaasService {
} }
@Path("login") @Path("login")
@POST @POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Consumes(MediaType.APPLICATION_FORM_URLENCODED)