iframe check login

This commit is contained in:
Bill Burke committed 2014-05-10 11:23:44 -04:00
1 parent 2a820c73b1
commit 1194e40ef2
9 files changed
+2098 -1906

No files matched your search

@@ -73,11 +73,32 @@ User <b id="subject"></b> made this request.
};
var reloadData = function () {
keycloak.updateToken(10).success(loadData).error(loadFailure);
keycloak.checkLoginIframe(
function() {
keycloak.updateToken(10).success(loadData).error(loadFailure);
},
function() {
document.getElementById('customers').innerHTML = '<b>Failed to load data. User is logged out.</b>';
//window.location.reload();
}
);
}
keycloak.onAuthSuccess = loadData;
keycloak.init('login-required');
//
// NOTE!!!: keycloak.setupCheckLoginIframe and checkLoginIframe are an optional way
// of discovering if you are logged out. This approach may not always be feasible, but it is a lightweight
// approach that requires no network call to determine if the user is logged in. The downside is that it
// requires a callback.
//
// Alternatively, on a single log out, the access token will eventually time out, then refresh token will fail
// as the session will be logged out on the auth server.
//
//
keycloak.init('login-required').success(function() {
// Use an iframe to detect if the user has done an SSO logout in a different window.
keycloak.setupCheckLoginIframe(document, window);
});
</script>
@@ -1,100 +1,100 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Keycloak Admin Console</title>
<link rel="icon" href="img/favicon.ico">
<link rel="stylesheet" href="css/styles.css">
<script src="lib/jquery/jquery-1.10.2.js" type="text/javascript"></script>
<script src="lib/select2-3.4.1/select2.js" type="text/javascript"></script>
<script src="lib/angular/angular.js"></script>
<script src="lib/angular/angular-resource.js"></script>
<script src="lib/angular/angular-route.js"></script>
<script src="lib/angular/ui-bootstrap-tpls-0.4.0.js"></script>
<script src="lib/jquery/jquery.idletimer.js" type="text/javascript"></script>
<script src="lib/jquery/jquery.idletimeout.js" type="text/javascript"></script>
<script src="lib/angular/select2.js" type="text/javascript"></script>
<script src="lib/fileupload/angular-file-upload.min.js"></script>
<script src="js/keycloak.js" type="text/javascript"></script>
<script src="js/app.js" type="text/javascript"></script>
<script src="js/controllers/realm.js" type="text/javascript"></script>
<script src="js/controllers/applications.js" type="text/javascript"></script>
<script src="js/controllers/oauth-clients.js" type="text/javascript"></script>
<script src="js/controllers/users.js" type="text/javascript"></script>
<script src="js/loaders.js" type="text/javascript"></script>
<script src="js/services.js" type="text/javascript"></script>
<style>
[ng\:cloak], [ng-cloak], .ng-cloak {
display: none !important;
}
</style>
</head>
<body class="admin-console" data-ng-controller="GlobalCtrl" data-ng-cloak>
<div id="idletimeout">
You will be logged off in <strong><span></span> seconds</strong> due to inactivity.
<a id="idletimeout-resume" href="#">Click here to continue using this web page</a>.
</div>
<div class="feedback-aligner" data-ng-show="notification" data-ng-click="notification = null">
<div class="alert alert-{{notification.type}}">
<span class="pficon pficon-ok" ng-show="notification.type == 'success'"></span>
<span class="pficon pficon-info" ng-show="notification.type == 'info'"></span>
<span class="pficon-layered" ng-show="notification.type == 'danger'">
<span class="pficon pficon-error-octagon"></span>
<span class="pficon pficon-error-exclamation"></span>
</span>
<span class="pficon-layered" ng-show="notification.type == 'warning'">
<span class="pficon pficon-warning-triangle"></span>
<span class="pficon pficon-warning-exclamation"></span>
</span>
<strong>{{notification.header}}</strong> {{notification.message}}
</div>
</div>
<header class="navbar navbar-default navbar-pf navbar-main header">
<div data-ng-include data-src="'partials/menu.html'"></div>
</header>
<div class="container">
<div data-ng-view id="view"></div>
<div id="loading" class="loading-backdrop">
<div class="loading">
<span>Loading...</span>
</div>
</div>
</div>
<script type="text/javascript">
$.idleTimeout('#idletimeout', '#idletimeout a', {
idleAfter: 300,
pollingInterval: 60,
// keepAliveURL: authUrl + '/admin/keepalive', would need to change this path
serverResponseEquals: '',
failedRequests: 1,
onTimeout: function(){
$(this).slideUp();
logout();
},
onIdle: function(){
$(this).slideDown(); // show the warning bar
},
onCountdown: function( counter ){
$(this).find("span").html( counter ); // update the counter
},
onResume: function(){
$(this).slideUp(); // hide the warning bar
}
});
</script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Keycloak Admin Console</title>
<link rel="icon" href="img/favicon.ico">
<link rel="stylesheet" href="css/styles.css">
<script src="lib/jquery/jquery-1.10.2.js" type="text/javascript"></script>
<script src="lib/select2-3.4.1/select2.js" type="text/javascript"></script>
<script src="lib/angular/angular.js"></script>
<script src="lib/angular/angular-resource.js"></script>
<script src="lib/angular/angular-route.js"></script>
<script src="lib/angular/ui-bootstrap-tpls-0.4.0.js"></script>
<script src="lib/jquery/jquery.idletimer.js" type="text/javascript"></script>
<script src="lib/jquery/jquery.idletimeout.js" type="text/javascript"></script>
<script src="lib/angular/select2.js" type="text/javascript"></script>
<script src="lib/fileupload/angular-file-upload.min.js"></script>
<script src="js/keycloak.js" type="text/javascript"></script>
<script src="js/app.js" type="text/javascript"></script>
<script src="js/controllers/realm.js" type="text/javascript"></script>
<script src="js/controllers/applications.js" type="text/javascript"></script>
<script src="js/controllers/oauth-clients.js" type="text/javascript"></script>
<script src="js/controllers/users.js" type="text/javascript"></script>
<script src="js/loaders.js" type="text/javascript"></script>
<script src="js/services.js" type="text/javascript"></script>
<style>
[ng\:cloak], [ng-cloak], .ng-cloak {
display: none !important;
}
</style>
</head>
<body class="admin-console" data-ng-controller="GlobalCtrl" data-ng-cloak>
<div id="idletimeout">
You will be logged off in <strong><span></span> seconds</strong> due to inactivity.
<a id="idletimeout-resume" href="#">Click here to continue using this web page</a>.
</div>
<div class="feedback-aligner" data-ng-show="notification" data-ng-click="notification = null">
<div class="alert alert-{{notification.type}}">
<span class="pficon pficon-ok" ng-show="notification.type == 'success'"></span>
<span class="pficon pficon-info" ng-show="notification.type == 'info'"></span>
<span class="pficon-layered" ng-show="notification.type == 'danger'">
<span class="pficon pficon-error-octagon"></span>
<span class="pficon pficon-error-exclamation"></span>
</span>
<span class="pficon-layered" ng-show="notification.type == 'warning'">
<span class="pficon pficon-warning-triangle"></span>
<span class="pficon pficon-warning-exclamation"></span>
</span>
<strong>{{notification.header}}</strong> {{notification.message}}
</div>
</div>
<header class="navbar navbar-default navbar-pf navbar-main header">
<div data-ng-include data-src="'partials/menu.html'"></div>
</header>
<div class="container">
<div data-ng-view id="view"></div>
<div id="loading" class="loading-backdrop">
<div class="loading">
<span>Loading...</span>
</div>
</div>
</div>
<script type="text/javascript">
$.idleTimeout('#idletimeout', '#idletimeout a', {
idleAfter: 300,
pollingInterval: 60,
// keepAliveURL: authUrl + '/admin/keepalive', would need to change this path
serverResponseEquals: '',
failedRequests: 1,
onTimeout: function(){
$(this).slideUp();
logout();
},
onIdle: function(){
$(this).slideDown(); // show the warning bar
},
onCountdown: function( counter ){
$(this).find("span").html( counter ); // update the counter
},
onResume: function(){
$(this).slideUp(); // hide the warning bar
}
});
</script>
</body>
</html>
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+31
View File
@@ -0,0 +1,31 @@
<script>
function getCookie(cname)
{
var name = cname + "=";
var ca = document.cookie.split(';');
for(var i=0; i<ca.length; i++)
{
var c = ca[i].trim();
if (c.indexOf(name)==0) return c.substring(name.length,c.length);
}
return null;
}
function receiveMessage(event)
{
if (event.origin !== "ORIGIN") {
console.log(event.origin + " does not match built origin");
return;
}
event.data.loggedIn = false;
var cookie = getCookie('KEYCLOAK_SESSION');
if (cookie) {
event.data.loggedIn = true;
event.data.session = cookie;
}
event.source.postMessage(event.data,
event.origin);
}
window.addEventListener("message", receiveMessage, false);
</script>
@@ -27,6 +27,7 @@ import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.HashSet;
@@ -41,7 +42,10 @@ import java.util.Set;
public class AuthenticationManager {
protected static Logger logger = Logger.getLogger(AuthenticationManager.class);
public static final String FORM_USERNAME = "username";
// used for auth login
public static final String KEYCLOAK_IDENTITY_COOKIE = "KEYCLOAK_IDENTITY";
// used solely to determine is user is logged in
public static final String KEYCLOAK_SESSION_COOKIE = "KEYCLOAK_SESSION";
public static final String KEYCLOAK_REMEMBER_ME = "KEYCLOAK_REMEMBER_ME";
protected ProviderSession providerSession;
@@ -72,18 +76,11 @@ public class AuthenticationManager {
return token;
}
public NewCookie createLoginCookie(RealmModel realm, UserModel user, UserSessionModel session, UriInfo uriInfo, boolean rememberMe) {
public void createLoginCookie(Response.ResponseBuilder builder, RealmModel realm, UserModel user, UserSessionModel session, UriInfo uriInfo, boolean rememberMe) {
logger.info("createLoginCookie");
String cookieName = KEYCLOAK_IDENTITY_COOKIE;
String cookiePath = getIdentityCookiePath(realm, uriInfo);
return createLoginCookie(realm, user, session, null, cookieName, cookiePath, rememberMe);
}
protected NewCookie createLoginCookie(RealmModel realm, UserModel user, UserSessionModel session, ClientModel client, String cookieName, String cookiePath, boolean rememberMe) {
AccessToken identityToken = createIdentityToken(realm, user, session);
if (client != null) {
identityToken.issuedFor(client.getClientId());
}
String encoded = encodeToken(realm, identityToken);
boolean secureOnly = !realm.isSslNotRequired();
logger.debugv("creatingLoginCookie - name: {0} path: {1}", cookieName, cookiePath);
@@ -92,8 +89,14 @@ public class AuthenticationManager {
maxAge = realm.getCentralLoginLifespan();
logger.info("createLoginCookie maxAge: " + maxAge);
}
NewCookie cookie = new NewCookie(cookieName, encoded, cookiePath, null, null, maxAge, secureOnly);// todo httponly , true);
return cookie;
builder.cookie(new NewCookie(cookieName, encoded, cookiePath, null, null, maxAge, secureOnly));// todo httponly , true);
String sessionCookieValue = realm.getName() + "-" + user.getId();
if (session != null) {
sessionCookieValue += "-" + session.getId();
}
builder.cookie(new NewCookie(KEYCLOAK_SESSION_COOKIE, sessionCookieValue, cookiePath, null, null, maxAge, secureOnly));// todo httponly , true);
}
public NewCookie createRememberMeCookie(RealmModel realm, UriInfo uriInfo) {
@@ -116,6 +119,7 @@ public class AuthenticationManager {
String path = getIdentityCookiePath(realm, uriInfo);
String cookieName = KEYCLOAK_IDENTITY_COOKIE;
expireCookie(cookieName, path);
expireCookie(KEYCLOAK_SESSION_COOKIE, path);
}
public void expireRememberMeCookie(RealmModel realm, UriInfo uriInfo) {
logger.debug("Expiring remember me cookie");
@@ -1,10 +1,13 @@
package org.keycloak.services.resources;
import org.jboss.logging.Logger;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.NotFoundException;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.jboss.resteasy.spi.UnauthorizedException;
import org.keycloak.audit.Audit;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
@@ -16,13 +19,21 @@ import org.keycloak.services.managers.BruteForceProtector;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.SocialRequestManager;
import org.keycloak.services.managers.TokenManager;
import org.keycloak.util.StreamUtil;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.io.InputStream;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -74,6 +85,63 @@ public class RealmsResource {
public static UriBuilder accountUrl(UriBuilder base) {
return base.path(RealmsResource.class).path(RealmsResource.class, "getAccountService");
}
/**
*
*
* @param name
* @param client_id
* @return
*/
@Path("{realm}/login-status-iframe.html")
@GET
@Produces(MediaType.TEXT_HTML)
@NoCache
public String getLoginStatusIframe(final @PathParam("realm") String name,
@QueryParam("client_id") String client_id) {
AuthenticationManager auth = new AuthenticationManager(providers);
//logger.info("getting login-status-iframe.html for client_id: " + client_id);
RealmManager realmManager = new RealmManager(session);
RealmModel realm = locateRealm(name, realmManager);
ClientModel client = realm.findClient(client_id);
if (client == null) {
throw new NotFoundException("could not find client: " + client_id);
}
AuthenticationManager.AuthResult result = auth.authenticateIdentityCookie(realm, uriInfo, headers);
if (result == null) {
throw new UnauthorizedException("not logged in, can't get page");
}
InputStream is = getClass().getClassLoader().getResourceAsStream("login-status-iframe.html");
if (is == null) throw new NotFoundException("Could not find login-status-iframe.html ");
Set<String> redirectUris = TokenService.resolveValidRedirects(uriInfo, client.getRedirectUris());
String origin = null;
for (String redirect : redirectUris) {
int index = redirect.indexOf("://");
if (index == -1) continue;
index = redirect.indexOf('/', index + 3);
if (index == -1) {
origin = redirect;
} else {
origin = redirect.substring(0, index);
}
break;
}
String file = null;
try {
file = StreamUtil.readString(is);
} catch (IOException e) {
throw new RuntimeException(e);
}
file = file.replace("ORIGIN", origin);
//System.out.println(file);
return file;
}
@Path("{realm}/tokens")
public TokenService getTokenService(final @PathParam("realm") String name) {
RealmManager realmManager = new RealmManager(session);
@@ -968,21 +968,8 @@ public class TokenService {
return redirectUri;
} else {
String r = redirectUri.indexOf('?') != -1 ? redirectUri.substring(0, redirectUri.indexOf('?')) : redirectUri;
Set<String> resolveValidRedirects = resolveValidRedirects(uriInfo, validRedirects);
// If the valid redirect URI is relative (no scheme, host, port) then use the request's scheme, host, and port
Set<String> resolveValidRedirects = new HashSet<String>();
for (String validRedirect : validRedirects) {
if (validRedirect.startsWith("/")) {
URI baseUri = uriInfo.getBaseUri();
String uri = baseUri.getScheme() + "://" + baseUri.getHost();
if (baseUri.getPort() != -1) {
uri += ":" + baseUri.getPort();
}
validRedirect = uri + validRedirect;
logger.debugv("replacing relative valid redirect with: {0}", validRedirect);
}
resolveValidRedirects.add(validRedirect);
}
boolean valid = matchesRedirects(resolveValidRedirects, r);
@@ -1005,6 +992,24 @@ public class TokenService {
}
}
public static Set<String> resolveValidRedirects(UriInfo uriInfo, Set<String> validRedirects) {
// If the valid redirect URI is relative (no scheme, host, port) then use the request's scheme, host, and port
Set<String> resolveValidRedirects = new HashSet<String>();
for (String validRedirect : validRedirects) {
if (validRedirect.startsWith("/")) {
URI baseUri = uriInfo.getBaseUri();
String uri = baseUri.getScheme() + "://" + baseUri.getHost();
if (baseUri.getPort() != -1) {
uri += ":" + baseUri.getPort();
}
validRedirect = uri + validRedirect;
logger.debugv("replacing relative valid redirect with: {0}", validRedirect);
}
resolveValidRedirects.add(validRedirect);
}
return resolveValidRedirects;
}
private boolean checkSsl() {
if (realm.isSslNotRequired()) {
return true;
@@ -93,7 +93,7 @@ public class OAuthFlows {
Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
Cookie remember = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
rememberMe = rememberMe || remember != null;
location.cookie(authManager.createLoginCookie(realm, accessCode.getUser(), session, uriInfo, rememberMe));
authManager.createLoginCookie(location, realm, accessCode.getUser(), session, uriInfo, rememberMe);
return location.build();
}
}