iframe check login
This commit is contained in:
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
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user