Merge pull request #1192 from patriot1burke/master
broker backchannel logout
This commit is contained in:
commit
08e1f455e2
29 changed files with 391 additions and 110 deletions
|
@ -63,6 +63,11 @@ public abstract class AbstractIdentityProvider<C extends IdentityProviderModel>
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void backchannelLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachUserSession(UserSessionModel userSession, ClientSessionModel clientSession, BrokeredIdentityContext context) {
|
||||
|
||||
|
|
|
@ -76,6 +76,8 @@ public interface IdentityProvider<C extends IdentityProviderModel> extends Provi
|
|||
*/
|
||||
Response retrieveToken(FederatedIdentityModel identity);
|
||||
|
||||
void backchannelLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm);
|
||||
|
||||
/**
|
||||
* Called when a Keycloak application initiates a logout through the browser. This is expected to do a logout
|
||||
* with the IDP
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
package org.keycloak.broker.oidc.util;
|
||||
|
||||
import org.codehaus.jackson.JsonNode;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
package org.keycloak.broker.provider.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
@ -21,14 +18,13 @@ import java.util.zip.GZIPInputStream;
|
|||
*/
|
||||
public class SimpleHttp {
|
||||
|
||||
private static ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
private String url;
|
||||
private String method;
|
||||
private Map<String, String> headers;
|
||||
private Map<String, String> params;
|
||||
|
||||
private SimpleHttp(String url, String method) {
|
||||
protected SimpleHttp(String url, String method) {
|
||||
this.url = url;
|
||||
this.method = method;
|
||||
}
|
||||
|
@ -57,9 +53,6 @@ public class SimpleHttp {
|
|||
return this;
|
||||
}
|
||||
|
||||
public JsonNode asJson() throws IOException {
|
||||
return mapper.readTree(asString());
|
||||
}
|
||||
|
||||
public String asString() throws IOException {
|
||||
boolean get = method.equals("GET");
|
||||
|
@ -126,11 +119,105 @@ public class SimpleHttp {
|
|||
return toString(is);
|
||||
} finally {
|
||||
if (os != null) {
|
||||
os.close();
|
||||
try {
|
||||
os.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (is != null) {
|
||||
is.close();
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
if (connection != null) {
|
||||
try {
|
||||
connection.disconnect();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int asStatus() throws IOException {
|
||||
boolean get = method.equals("GET");
|
||||
boolean post = method.equals("POST");
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (get) {
|
||||
sb.append(url);
|
||||
}
|
||||
|
||||
if (params != null) {
|
||||
boolean f = true;
|
||||
for (Map.Entry<String, String> p : params.entrySet()) {
|
||||
if (f) {
|
||||
f = false;
|
||||
if (get) {
|
||||
sb.append("?");
|
||||
}
|
||||
} else {
|
||||
sb.append("&");
|
||||
}
|
||||
sb.append(URLEncoder.encode(p.getKey(), "UTF-8"));
|
||||
sb.append("=");
|
||||
sb.append(URLEncoder.encode(p.getValue(), "UTF-8"));
|
||||
}
|
||||
}
|
||||
|
||||
if (get) {
|
||||
url = sb.toString();
|
||||
}
|
||||
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||
OutputStream os = null;
|
||||
InputStream is = null;
|
||||
|
||||
try {
|
||||
connection.setRequestMethod(method);
|
||||
|
||||
if (headers != null) {
|
||||
for (Map.Entry<String, String> h : headers.entrySet()) {
|
||||
connection.setRequestProperty(h.getKey(), h.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
if (post) {
|
||||
String data = sb.toString();
|
||||
|
||||
connection.setDoOutput(true);
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
|
||||
connection.setRequestProperty("Content-Length", String.valueOf(data.length()));
|
||||
|
||||
os = connection.getOutputStream();
|
||||
os.write(data.getBytes());
|
||||
} else {
|
||||
connection.setDoOutput(false);
|
||||
}
|
||||
|
||||
is = connection.getInputStream();
|
||||
return connection.getResponseCode();
|
||||
} finally {
|
||||
if (os != null) {
|
||||
try {
|
||||
os.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (is != null) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
if (connection != null) {
|
||||
try {
|
||||
connection.disconnect();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,7 +22,7 @@ import org.codehaus.jackson.map.ObjectMapper;
|
|||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.ClientConnection;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.broker.oidc.util.SimpleHttp;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
import org.keycloak.broker.provider.AbstractIdentityProvider;
|
||||
import org.keycloak.broker.provider.AuthenticationRequest;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package org.keycloak.broker.oidc;
|
||||
|
||||
import org.keycloak.broker.oidc.util.SimpleHttp;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.constants.AdapterConstants;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
|
@ -74,7 +74,7 @@ public class KeycloakOIDCIdentityProvider extends OIDCIdentityProvider {
|
|||
&& userSession.getState() != UserSessionModel.State.LOGGING_OUT
|
||||
&& userSession.getState() != UserSessionModel.State.LOGGED_OUT
|
||||
) {
|
||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,8 @@ package org.keycloak.broker.oidc;
|
|||
|
||||
import org.codehaus.jackson.JsonNode;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.broker.oidc.util.SimpleHttp;
|
||||
import org.keycloak.broker.oidc.util.JsonSimpleHttp;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
import org.keycloak.broker.provider.AuthenticationRequest;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
|
@ -127,20 +128,51 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
|
|||
}
|
||||
|
||||
@Override
|
||||
public Response keycloakInitiatedBrowserLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
|
||||
if (getConfig().getLogoutUrl() == null || getConfig().getLogoutUrl().trim().equals("")) return null;
|
||||
public void backchannelLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
|
||||
if (getConfig().getLogoutUrl() == null || getConfig().getLogoutUrl().trim().equals("") || !getConfig().isBackchannelSupported()) return;
|
||||
String idToken = userSession.getNote(FEDERATED_ID_TOKEN);
|
||||
if (idToken == null) return;
|
||||
backchannelLogout(userSession, idToken);
|
||||
}
|
||||
|
||||
protected void backchannelLogout(UserSessionModel userSession, String idToken) {
|
||||
String sessionId = userSession.getId();
|
||||
UriBuilder logoutUri = UriBuilder.fromUri(getConfig().getLogoutUrl())
|
||||
.queryParam("state", sessionId);
|
||||
.queryParam("state", sessionId);
|
||||
logoutUri.queryParam("id_token_hint", idToken);
|
||||
String url = logoutUri.build().toString();
|
||||
try {
|
||||
int status = JsonSimpleHttp.doGet(url).asStatus();
|
||||
boolean success = status >=200 && status < 400;
|
||||
if (!success) {
|
||||
logger.warn("Failed backchannel broker logout to: " + url);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed backchannel broker logout to: " + url, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Response keycloakInitiatedBrowserLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
|
||||
if (getConfig().getLogoutUrl() == null || getConfig().getLogoutUrl().trim().equals("")) return null;
|
||||
String idToken = userSession.getNote(FEDERATED_ID_TOKEN);
|
||||
if (idToken != null) logoutUri.queryParam("id_token_hint", idToken);
|
||||
String redirect = RealmsResource.brokerUrl(uriInfo)
|
||||
.path(IdentityBrokerService.class, "getEndpoint")
|
||||
.path(OIDCEndpoint.class, "logoutResponse")
|
||||
.build(realm.getName(), getConfig().getAlias()).toString();
|
||||
logoutUri.queryParam("post_logout_redirect_uri", redirect);
|
||||
Response response = Response.status(302).location(logoutUri.build()).build();
|
||||
return response;
|
||||
if (idToken != null && getConfig().isBackchannelSupported()) {
|
||||
backchannelLogout(userSession, idToken);
|
||||
return null;
|
||||
} else {
|
||||
String sessionId = userSession.getId();
|
||||
UriBuilder logoutUri = UriBuilder.fromUri(getConfig().getLogoutUrl())
|
||||
.queryParam("state", sessionId);
|
||||
if (idToken != null) logoutUri.queryParam("id_token_hint", idToken);
|
||||
String redirect = RealmsResource.brokerUrl(uriInfo)
|
||||
.path(IdentityBrokerService.class, "getEndpoint")
|
||||
.path(OIDCEndpoint.class, "logoutResponse")
|
||||
.build(realm.getName(), getConfig().getAlias()).toString();
|
||||
logoutUri.queryParam("post_logout_redirect_uri", redirect);
|
||||
Response response = Response.status(302).location(logoutUri.build()).build();
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -184,9 +216,9 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
|
|||
String email = (String)idToken.getOtherClaims().get(IDToken.EMAIL);
|
||||
|
||||
if (getConfig().getUserInfoUrl() != null && (id == null || name == null || preferredUsername == null || email == null) ) {
|
||||
JsonNode userInfo = SimpleHttp.doGet(getConfig().getUserInfoUrl())
|
||||
.header("Authorization", "Bearer " + accessToken)
|
||||
.asJson();
|
||||
SimpleHttp request = JsonSimpleHttp.doGet(getConfig().getUserInfoUrl())
|
||||
.header("Authorization", "Bearer " + accessToken);
|
||||
JsonNode userInfo = JsonSimpleHttp.asJson(request);
|
||||
|
||||
id = getJsonProperty(userInfo, "sub");
|
||||
name = getJsonProperty(userInfo, "name");
|
||||
|
|
|
@ -70,6 +70,14 @@ public class OIDCIdentityProviderConfig extends OAuth2IdentityProviderConfig {
|
|||
getConfig().put("validateSignature", String.valueOf(validateSignature));
|
||||
}
|
||||
|
||||
public boolean isBackchannelSupported() {
|
||||
return Boolean.valueOf(getConfig().get("backchannelSupported"));
|
||||
}
|
||||
|
||||
public void setBackchannelSupported(boolean backchannel) {
|
||||
getConfig().put("backchannelSupported", String.valueOf(backchannel));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -17,11 +17,10 @@
|
|||
*/
|
||||
package org.keycloak.broker.oidc;
|
||||
|
||||
import org.keycloak.broker.oidc.util.SimpleHttp;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
|
||||
import org.keycloak.jose.jwk.JWK;
|
||||
import org.keycloak.jose.jwk.JWKParser;
|
||||
import org.keycloak.jose.jws.Algorithm;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.oidc.representations.JSONWebKeySet;
|
||||
|
|
32
broker/oidc/src/main/java/org/keycloak/broker/oidc/util/JsonSimpleHttp.java
Executable file
32
broker/oidc/src/main/java/org/keycloak/broker/oidc/util/JsonSimpleHttp.java
Executable file
|
@ -0,0 +1,32 @@
|
|||
package org.keycloak.broker.oidc.util;
|
||||
|
||||
import org.codehaus.jackson.JsonNode;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class JsonSimpleHttp extends SimpleHttp {
|
||||
public JsonSimpleHttp(String url, String method) {
|
||||
super(url, method);
|
||||
}
|
||||
|
||||
public static JsonSimpleHttp doGet(String url) {
|
||||
return new JsonSimpleHttp(url, "GET");
|
||||
}
|
||||
|
||||
public static JsonSimpleHttp doPost(String url) {
|
||||
return new JsonSimpleHttp(url, "POST");
|
||||
}
|
||||
|
||||
private static ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
public static JsonNode asJson(SimpleHttp request) throws IOException {
|
||||
return mapper.readTree(request.asString());
|
||||
}
|
||||
|
||||
}
|
|
@ -218,7 +218,7 @@ public class SAMLEndpoint {
|
|||
List<UserSessionModel> userSessions = session.sessions().getUserSessionByBrokerUserId(realm, brokerUserId);
|
||||
for (UserSessionModel userSession : userSessions) {
|
||||
try {
|
||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, false);
|
||||
} catch (Exception e) {
|
||||
logger.warn("failed to do backchannel logout for userSession", e);
|
||||
}
|
||||
|
@ -230,7 +230,7 @@ public class SAMLEndpoint {
|
|||
UserSessionModel userSession = session.sessions().getUserSessionByBrokerSessionId(realm, brokerSessionId);
|
||||
if (userSession != null) {
|
||||
try {
|
||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, false);
|
||||
} catch (Exception e) {
|
||||
logger.warn("failed to do backchannel logout for userSession", e);
|
||||
}
|
||||
|
|
|
@ -17,10 +17,12 @@
|
|||
*/
|
||||
package org.keycloak.broker.saml;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.broker.provider.AbstractIdentityProvider;
|
||||
import org.keycloak.broker.provider.AuthenticationRequest;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
import org.keycloak.dom.saml.v2.assertion.AssertionType;
|
||||
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
|
||||
import org.keycloak.dom.saml.v2.assertion.NameIDType;
|
||||
|
@ -34,12 +36,17 @@ import org.keycloak.models.UserSessionModel;
|
|||
import org.keycloak.protocol.saml.SAML2AuthnRequestBuilder;
|
||||
import org.keycloak.protocol.saml.SAML2LogoutRequestBuilder;
|
||||
import org.keycloak.protocol.saml.SAML2NameIDPolicyBuilder;
|
||||
import org.keycloak.saml.common.constants.GeneralConstants;
|
||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
||||
import org.keycloak.saml.common.exceptions.ParsingException;
|
||||
import org.keycloak.saml.common.exceptions.ProcessingException;
|
||||
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
|
@ -48,6 +55,7 @@ import java.security.PublicKey;
|
|||
* @author Pedro Igor
|
||||
*/
|
||||
public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityProviderConfig> {
|
||||
protected static final Logger logger = Logger.getLogger(SAMLIdentityProvider.class);
|
||||
public SAMLIdentityProvider(SAMLIdentityProviderConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
@ -141,25 +149,56 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||
}
|
||||
|
||||
@Override
|
||||
public Response keycloakInitiatedBrowserLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
|
||||
if (getConfig().getSingleLogoutServiceUrl() == null || getConfig().getSingleLogoutServiceUrl().trim().equals("")) return null;
|
||||
public void backchannelLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
|
||||
String singleLogoutServiceUrl = getConfig().getSingleLogoutServiceUrl();
|
||||
if (singleLogoutServiceUrl == null || singleLogoutServiceUrl.trim().equals("") || !getConfig().isBackchannelSupported()) return;
|
||||
SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
|
||||
try {
|
||||
int status = SimpleHttp.doPost(singleLogoutServiceUrl)
|
||||
.param(GeneralConstants.SAML_REQUEST_KEY, logoutBuilder.postBinding().encoded())
|
||||
.param(GeneralConstants.RELAY_STATE, userSession.getId()).asStatus();
|
||||
boolean success = status >=200 && status < 400;
|
||||
if (!success) {
|
||||
logger.warn("Failed saml backchannel broker logout to: " + singleLogoutServiceUrl);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed saml backchannel broker logout to: " + singleLogoutServiceUrl, e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response keycloakInitiatedBrowserLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
|
||||
String singleLogoutServiceUrl = getConfig().getSingleLogoutServiceUrl();
|
||||
if (singleLogoutServiceUrl == null || singleLogoutServiceUrl.trim().equals("")) return null;
|
||||
|
||||
if (getConfig().isBackchannelSupported()) {
|
||||
backchannelLogout(userSession, uriInfo, realm);
|
||||
return null;
|
||||
} else {
|
||||
try {
|
||||
SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
|
||||
return logoutBuilder.postBinding().request();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected SAML2LogoutRequestBuilder buildLogoutRequest(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm, String singleLogoutServiceUrl) {
|
||||
SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
|
||||
.assertionExpiration(realm.getAccessCodeLifespan())
|
||||
.issuer(getEntityId(uriInfo, realm))
|
||||
.sessionIndex(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SESSION_INDEX))
|
||||
.userPrincipal(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT), userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT_NAMEFORMAT))
|
||||
.destination(getConfig().getSingleLogoutServiceUrl());
|
||||
.destination(singleLogoutServiceUrl)
|
||||
.relayState(userSession.getId());
|
||||
if (getConfig().isWantAuthnRequestsSigned()) {
|
||||
logoutBuilder.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
||||
.signDocument();
|
||||
}
|
||||
try {
|
||||
return logoutBuilder.relayState(userSession.getId()).postBinding().request();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return logoutBuilder;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -110,4 +110,13 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
|
|||
public void setPostBindingResponse(boolean postBindingResponse) {
|
||||
getConfig().put("postBindingResponse", String.valueOf(postBindingResponse));
|
||||
}
|
||||
|
||||
public boolean isBackchannelSupported() {
|
||||
return Boolean.valueOf(getConfig().get("backchannelSupported"));
|
||||
}
|
||||
|
||||
public void setBackchannelSupported(boolean backchannel) {
|
||||
getConfig().put("backchannelSupported", String.valueOf(backchannel));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
<span tooltip-placement="right" tooltip="Indicates if this provider should be tried by default for authentication even before displaying login screen" class="fa fa-info-circle"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="enabled">Store Tokens</label>
|
||||
<label class="col-sm-2 control-label" for="storeToken">Store Tokens</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="identityProvider.storeToken" id="storeToken" onoffswitch />
|
||||
</div>
|
||||
|
@ -93,6 +93,13 @@
|
|||
</div>
|
||||
<span tooltip-placement="right" tooltip="End session endpoint to use to logout user from external IDP." class="fa fa-info-circle"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="backchannelSupported">Backchannel Logout</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="identityProvider.config.backchannelSupported" id="backchannelSupported" value="'true'" onoffswitchvalue />
|
||||
</div>
|
||||
<span tooltip-placement="right" tooltip="Does the external IDP support backchannel logout?" class="fa fa-info-circle"></span>
|
||||
</div>
|
||||
<div class="form-group clearfix">
|
||||
<label class="col-sm-2 control-label" for="userInfoUrl">User Info Url</label>
|
||||
<div class="col-sm-4">
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
<span tooltip-placement="right" tooltip="Indicates if this provider should be tried by default for authentication even before displaying login screen" class="fa fa-info-circle"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="enabled">Store Tokens</label>
|
||||
<label class="col-sm-2 control-label" for="storeToken">Store Tokens</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="identityProvider.storeToken" id="storeToken" onoffswitch />
|
||||
</div>
|
||||
|
@ -87,6 +87,13 @@
|
|||
</div>
|
||||
<span tooltip-placement="right" tooltip="The Url that must be used to send logout requests." class="fa fa-info-circle"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="backchannelSupported">Backchannel Logout</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="identityProvider.config.backchannelSupported" id="backchannelSupported" value="'true'" onoffswitchvalue />
|
||||
</div>
|
||||
<span tooltip-placement="right" tooltip="Does the external IDP support backchannel logout?" class="fa fa-info-circle"></span>
|
||||
</div>
|
||||
<div class="form-group clearfix">
|
||||
<label class="col-sm-2 control-label" for="nameIDPolicyFormat">NameID Policy Format</label>
|
||||
<div class="col-sm-4">
|
||||
|
|
|
@ -39,6 +39,7 @@ import static org.keycloak.saml.common.util.StringUtil.isNotNull;
|
|||
*/
|
||||
public class SAML2BindingBuilder<T extends SAML2BindingBuilder> {
|
||||
protected static final Logger logger = Logger.getLogger(SAML2BindingBuilder.class);
|
||||
public static final String RELAY_STATE = "RelayState";
|
||||
|
||||
protected KeyPair signingKeyPair;
|
||||
protected X509Certificate signingCertificate;
|
||||
|
@ -328,7 +329,7 @@ public class SAML2BindingBuilder<T extends SAML2BindingBuilder> {
|
|||
builder.append("<INPUT TYPE=\"HIDDEN\" NAME=\"" + key + "\"" + " VALUE=\"" + samlResponse + "\"/>");
|
||||
|
||||
if (isNotNull(relayState)) {
|
||||
builder.append("<INPUT TYPE=\"HIDDEN\" NAME=\"RelayState\" " + "VALUE=\"" + escapeAttribute(relayState) + "\"/>");
|
||||
builder.append("<INPUT TYPE=\"HIDDEN\" NAME=\"" + RELAY_STATE + "\" " + "VALUE=\"" + escapeAttribute(relayState) + "\"/>");
|
||||
}
|
||||
|
||||
builder.append("<NOSCRIPT>");
|
||||
|
@ -355,7 +356,7 @@ public class SAML2BindingBuilder<T extends SAML2BindingBuilder> {
|
|||
.replaceQuery(null)
|
||||
.queryParam(samlParameterName, base64Encoded(document));
|
||||
if (relayState != null) {
|
||||
builder.queryParam("RelayState", relayState);
|
||||
builder.queryParam(RELAY_STATE, relayState);
|
||||
}
|
||||
|
||||
if (sign) {
|
||||
|
|
|
@ -161,7 +161,7 @@ public class SamlProtocol implements LoginProtocol {
|
|||
return SamlProtocol.SAML_POST_BINDING.equals(clientSession.getNote(SamlProtocol.SAML_BINDING)) || forcePostBinding(client);
|
||||
}
|
||||
|
||||
protected boolean isLogoutPostBindingForInitiator(UserSessionModel session) {
|
||||
public static boolean isLogoutPostBindingForInitiator(UserSessionModel session) {
|
||||
String note = session.getNote(SamlProtocol.SAML_LOGOUT_BINDING);
|
||||
return SamlProtocol.SAML_POST_BINDING.equals(note);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,12 @@ import org.jboss.resteasy.spi.HttpRequest;
|
|||
import org.jboss.resteasy.spi.HttpResponse;
|
||||
import org.keycloak.ClientConnection;
|
||||
import org.keycloak.VerificationException;
|
||||
import org.keycloak.dom.saml.v2.SAML2Object;
|
||||
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
|
||||
import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
|
||||
import org.keycloak.dom.saml.v2.protocol.NameIDPolicyType;
|
||||
import org.keycloak.dom.saml.v2.protocol.RequestAbstractType;
|
||||
import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
|
@ -18,22 +24,18 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
||||
import org.keycloak.saml.common.constants.GeneralConstants;
|
||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
||||
import org.keycloak.saml.common.exceptions.ProcessingException;
|
||||
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
|
||||
import org.keycloak.services.ErrorPage;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.managers.HttpAuthenticationManager;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.resources.RealmsResource;
|
||||
import org.keycloak.services.ErrorPage;
|
||||
import org.keycloak.util.StreamUtil;
|
||||
import org.keycloak.saml.common.constants.GeneralConstants;
|
||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
|
||||
import org.keycloak.dom.saml.v2.SAML2Object;
|
||||
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
|
||||
import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
|
||||
import org.keycloak.dom.saml.v2.protocol.NameIDPolicyType;
|
||||
import org.keycloak.dom.saml.v2.protocol.RequestAbstractType;
|
||||
import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.FormParam;
|
||||
|
@ -51,6 +53,7 @@ import javax.ws.rs.core.SecurityContext;
|
|||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import javax.ws.rs.ext.Providers;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.security.PublicKey;
|
||||
|
@ -122,7 +125,7 @@ public class SamlService {
|
|||
protected Response handleSamlResponse(String samlResponse, String relayState) {
|
||||
event.event(EventType.LOGOUT);
|
||||
SAMLDocumentHolder holder = extractResponseDocument(samlResponse);
|
||||
StatusResponseType statusResponse = (StatusResponseType)holder.getSamlObject();
|
||||
StatusResponseType statusResponse = (StatusResponseType) holder.getSamlObject();
|
||||
// validate destination
|
||||
if (!uriInfo.getAbsolutePath().toString().equals(statusResponse.getDestination())) {
|
||||
event.error(Errors.INVALID_SAML_LOGOUT_RESPONSE);
|
||||
|
@ -162,7 +165,7 @@ public class SamlService {
|
|||
|
||||
SAML2Object samlObject = documentHolder.getSamlObject();
|
||||
|
||||
RequestAbstractType requestAbstractType = (RequestAbstractType)samlObject;
|
||||
RequestAbstractType requestAbstractType = (RequestAbstractType) samlObject;
|
||||
String issuer = requestAbstractType.getIssuer().getValue();
|
||||
ClientModel client = realm.getClientByClientId(issuer);
|
||||
|
||||
|
@ -177,7 +180,7 @@ public class SamlService {
|
|||
event.error(Errors.CLIENT_DISABLED);
|
||||
return ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED);
|
||||
}
|
||||
if ((client instanceof ClientModel) && ((ClientModel)client).isBearerOnly()) {
|
||||
if ((client instanceof ClientModel) && ((ClientModel) client).isBearerOnly()) {
|
||||
event.event(EventType.LOGIN);
|
||||
event.error(Errors.NOT_ALLOWED);
|
||||
return ErrorPage.error(session, Messages.BEARER_ONLY);
|
||||
|
@ -221,6 +224,7 @@ public class SamlService {
|
|||
protected abstract void verifySignature(SAMLDocumentHolder documentHolder, ClientModel client) throws VerificationException;
|
||||
|
||||
protected abstract SAMLDocumentHolder extractRequestDocument(String samlRequest);
|
||||
|
||||
protected abstract SAMLDocumentHolder extractResponseDocument(String response);
|
||||
|
||||
protected Response loginRequest(String relayState, AuthnRequestType requestAbstractType, ClientModel client) {
|
||||
|
@ -231,7 +235,8 @@ public class SamlService {
|
|||
return ErrorPage.error(session, Messages.INVALID_REQUEST);
|
||||
}
|
||||
String bindingType = getBindingType(requestAbstractType);
|
||||
if ("true".equals(client.getAttribute(SamlProtocol.SAML_FORCE_POST_BINDING))) bindingType = SamlProtocol.SAML_POST_BINDING;
|
||||
if ("true".equals(client.getAttribute(SamlProtocol.SAML_FORCE_POST_BINDING)))
|
||||
bindingType = SamlProtocol.SAML_POST_BINDING;
|
||||
String redirect = null;
|
||||
URI redirectUri = requestAbstractType.getAssertionConsumerServiceURL();
|
||||
if (redirectUri != null && !"null".equals(redirectUri)) { // "null" is for testing purposes
|
||||
|
@ -243,7 +248,7 @@ public class SamlService {
|
|||
redirect = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE);
|
||||
}
|
||||
if (redirect == null && client instanceof ClientModel) {
|
||||
redirect = ((ClientModel)client).getManagementUrl();
|
||||
redirect = ((ClientModel) client).getManagementUrl();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -265,10 +270,10 @@ public class SamlService {
|
|||
|
||||
// Handle NameIDPolicy from SP
|
||||
NameIDPolicyType nameIdPolicy = requestAbstractType.getNameIDPolicy();
|
||||
if(nameIdPolicy != null && !SamlProtocol.forceNameIdFormat(client)) {
|
||||
if (nameIdPolicy != null && !SamlProtocol.forceNameIdFormat(client)) {
|
||||
String nameIdFormat = nameIdPolicy.getFormat().toString();
|
||||
// TODO: Handle AllowCreate too, relevant for persistent NameID.
|
||||
if(isSupportedNameIdFormat(nameIdFormat)) {
|
||||
if (isSupportedNameIdFormat(nameIdFormat)) {
|
||||
clientSession.setNote(GeneralConstants.NAMEID_FORMAT, nameIdFormat);
|
||||
} else {
|
||||
event.error(Errors.INVALID_SAML_AUTHN_REQUEST);
|
||||
|
@ -344,7 +349,8 @@ public class SamlService {
|
|||
AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers, false);
|
||||
if (authResult != null) {
|
||||
String logoutBinding = getBindingType();
|
||||
if ("true".equals(client.getAttribute(SamlProtocol.SAML_FORCE_POST_BINDING))) logoutBinding = SamlProtocol.SAML_POST_BINDING;
|
||||
if ("true".equals(client.getAttribute(SamlProtocol.SAML_FORCE_POST_BINDING)))
|
||||
logoutBinding = SamlProtocol.SAML_POST_BINDING;
|
||||
String bindingUri = SamlProtocol.getLogoutServiceUrl(uriInfo, client, logoutBinding);
|
||||
UserSessionModel userSession = authResult.getSession();
|
||||
userSession.setNote(SamlProtocol.SAML_LOGOUT_BINDING_URI, bindingUri);
|
||||
|
@ -364,33 +370,51 @@ public class SamlService {
|
|||
}
|
||||
logger.debug("browser Logout");
|
||||
return authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||
}
|
||||
} else if (logoutRequest.getSessionIndex() != null) {
|
||||
for (String sessionIndex : logoutRequest.getSessionIndex()) {
|
||||
ClientSessionModel clientSession = session.sessions().getClientSession(realm, sessionIndex);
|
||||
if (clientSession == null) continue;
|
||||
if (clientSession.getClient().getClientId().equals(client.getClientId())) {
|
||||
// remove requesting client from logout
|
||||
clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT);
|
||||
}
|
||||
UserSessionModel userSession = clientSession.getUserSession();
|
||||
try {
|
||||
authManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failure with backchannel logout", e);
|
||||
}
|
||||
|
||||
|
||||
String redirectUri = null;
|
||||
|
||||
if (client instanceof ClientModel) {
|
||||
redirectUri = ((ClientModel)client).getBaseUrl();
|
||||
}
|
||||
|
||||
if (redirectUri != null) {
|
||||
redirectUri = RedirectUtils.verifyRedirectUri(uriInfo, redirectUri, realm, client);
|
||||
if (redirectUri == null) {
|
||||
return ErrorPage.error(session, Messages.INVALID_REDIRECT_URI);
|
||||
}
|
||||
}
|
||||
if (redirectUri != null) {
|
||||
return Response.status(302).location(UriBuilder.fromUri(redirectUri).build()).build();
|
||||
} else {
|
||||
return Response.ok().build();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
// default
|
||||
|
||||
private Response logout(UserSessionModel userSession) {
|
||||
Response response = authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||
if (response == null) event.user(userSession.getUser()).session(userSession).success();
|
||||
return response;
|
||||
String logoutBinding = getBindingType();
|
||||
String logoutBindingUri = SamlProtocol.getLogoutServiceUrl(uriInfo, client, logoutBinding);
|
||||
String logoutRelayState = relayState;
|
||||
SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
|
||||
builder.logoutRequestID(logoutRequest.getID());
|
||||
builder.destination(logoutBindingUri);
|
||||
builder.issuer(RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString());
|
||||
builder.relayState(logoutRelayState);
|
||||
if (SamlProtocol.requiresRealmSignature(client)) {
|
||||
SignatureAlgorithm algorithm = SamlProtocol.getSignatureAlgorithm(client);
|
||||
builder.signatureAlgorithm(algorithm)
|
||||
.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
||||
.signDocument();
|
||||
|
||||
}
|
||||
try {
|
||||
if (SamlProtocol.SAML_POST_BINDING.equals(logoutBinding)) {
|
||||
return builder.postBinding().response(logoutBindingUri);
|
||||
} else {
|
||||
return builder.redirectBinding().response(logoutBindingUri);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkSsl() {
|
||||
|
@ -414,6 +438,7 @@ public class SamlService {
|
|||
protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
|
||||
return SAMLRequestParser.parseRequestPostBinding(samlRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SAMLDocumentHolder extractResponseDocument(String response) {
|
||||
return SAMLRequestParser.parseResponsePostBinding(response);
|
||||
|
@ -446,7 +471,6 @@ public class SamlService {
|
|||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
|
||||
return SAMLRequestParser.parseRequestRedirectBinding(samlRequest);
|
||||
|
@ -478,7 +502,7 @@ public class SamlService {
|
|||
@GET
|
||||
public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
|
||||
@QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
|
||||
@QueryParam(GeneralConstants.RELAY_STATE) String relayState) {
|
||||
@QueryParam(GeneralConstants.RELAY_STATE) String relayState) {
|
||||
logger.debug("SAML GET");
|
||||
return new RedirectBindingProtocol().execute(samlRequest, samlResponse, relayState);
|
||||
}
|
||||
|
|
|
@ -86,7 +86,7 @@ public class TokenManager {
|
|||
|
||||
UserSessionModel userSession = session.sessions().getUserSession(realm, oldToken.getSessionState());
|
||||
if (!AuthenticationManager.isSessionValid(realm, userSession)) {
|
||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, connection, headers);
|
||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, connection, headers, true);
|
||||
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Session not active", "Session not active");
|
||||
}
|
||||
ClientSessionModel clientSession = null;
|
||||
|
|
|
@ -126,7 +126,7 @@ public class LogoutEndpoint {
|
|||
return AuthenticationManager.browserLogout(session, realm, authResult.getSession(), uriInfo, clientConnection, headers);
|
||||
} else if (userSession != null) { // non browser logout
|
||||
event.event(EventType.LOGOUT);
|
||||
authManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||
authManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
|
||||
event.user(userSession.getUser()).session(userSession).success();
|
||||
}
|
||||
|
||||
|
@ -183,7 +183,7 @@ public class LogoutEndpoint {
|
|||
}
|
||||
|
||||
private void logout(UserSessionModel userSession) {
|
||||
authManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||
authManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
|
||||
event.user(userSession.getUser()).session(userSession).success();
|
||||
}
|
||||
|
||||
|
|
|
@ -104,7 +104,20 @@ public class AuthenticationManager {
|
|||
|
||||
}
|
||||
|
||||
public static void backchannelLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
|
||||
/**
|
||||
* Do not logout broker
|
||||
*
|
||||
* @param session
|
||||
* @param realm
|
||||
* @param userSession
|
||||
* @param uriInfo
|
||||
* @param connection
|
||||
* @param headers
|
||||
*/
|
||||
public static void backchannelLogout(KeycloakSession session, RealmModel realm,
|
||||
UserSessionModel userSession, UriInfo uriInfo,
|
||||
ClientConnection connection, HttpHeaders headers,
|
||||
boolean logoutBroker) {
|
||||
if (userSession == null) return;
|
||||
UserModel user = userSession.getUser();
|
||||
userSession.setState(UserSessionModel.State.LOGGING_OUT);
|
||||
|
@ -115,6 +128,16 @@ public class AuthenticationManager {
|
|||
for (ClientSessionModel clientSession : userSession.getClientSessions()) {
|
||||
backchannelLogoutClientSession(session, realm, clientSession, userSession, uriInfo, headers);
|
||||
}
|
||||
if (logoutBroker) {
|
||||
String brokerId = userSession.getNote(IdentityBrokerService.BROKER_PROVIDER_ID);
|
||||
if (brokerId != null) {
|
||||
IdentityProvider identityProvider = IdentityBrokerService.getIdentityProvider(session, realm, brokerId);
|
||||
try {
|
||||
identityProvider.backchannelLogout(userSession, uriInfo, realm);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
userSession.setState(UserSessionModel.State.LOGGED_OUT);
|
||||
session.sessions().removeUserSession(realm, userSession);
|
||||
}
|
||||
|
@ -131,8 +154,8 @@ public class AuthenticationManager {
|
|||
protocol.backchannelLogout(userSession, clientSession);
|
||||
clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static Response browserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
|
||||
if (userSession == null) return null;
|
||||
|
@ -525,7 +548,7 @@ public class AuthenticationManager {
|
|||
|
||||
UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionState());
|
||||
if (!isSessionValid(realm, userSession)) {
|
||||
if (userSession != null) backchannelLogout(session, realm, userSession, uriInfo, connection, headers);
|
||||
if (userSession != null) backchannelLogout(session, realm, userSession, uriInfo, connection, headers, true);
|
||||
logger.debug("User session not active");
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -480,7 +480,7 @@ public class AccountService {
|
|||
UserModel user = auth.getUser();
|
||||
List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
|
||||
for (UserSessionModel userSession : userSessions) {
|
||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
|
||||
}
|
||||
|
||||
UriBuilder builder = Urls.accountBase(uriInfo.getBaseUri()).path(AccountService.class, "sessionsPage");
|
||||
|
@ -675,7 +675,7 @@ public class AccountService {
|
|||
List<UserSessionModel> sessions = session.sessions().getUserSessions(realm, user);
|
||||
for (UserSessionModel s : sessions) {
|
||||
if (!s.getId().equals(auth.getSession().getId())) {
|
||||
AuthenticationManager.backchannelLogout(session, realm, s, uriInfo, clientConnection, headers);
|
||||
AuthenticationManager.backchannelLogout(session, realm, s, uriInfo, clientConnection, headers, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -595,7 +595,7 @@ public class LoginActionsService {
|
|||
}
|
||||
|
||||
if (!AuthenticationManager.isSessionValid(realm, userSession)) {
|
||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
|
||||
event.error(Errors.INVALID_CODE);
|
||||
return ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE);
|
||||
}
|
||||
|
|
|
@ -280,7 +280,7 @@ public class RealmAdminResource {
|
|||
public void deleteSession(@PathParam("session") String sessionId) {
|
||||
UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId);
|
||||
if (userSession == null) throw new NotFoundException("Sesssion not found");
|
||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, connection, headers);
|
||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, connection, headers, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -379,7 +379,7 @@ public class UsersResource {
|
|||
|
||||
List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
|
||||
for (UserSessionModel userSession : userSessions) {
|
||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@ package org.keycloak.social.facebook;
|
|||
import org.codehaus.jackson.JsonNode;
|
||||
import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
|
||||
import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
|
||||
import org.keycloak.broker.oidc.util.SimpleHttp;
|
||||
import org.keycloak.broker.oidc.util.JsonSimpleHttp;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.social.SocialIdentityProvider;
|
||||
|
@ -27,7 +28,7 @@ public class FacebookIdentityProvider extends AbstractOAuth2IdentityProvider imp
|
|||
|
||||
protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
|
||||
try {
|
||||
JsonNode profile = SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken).asJson();
|
||||
JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken));
|
||||
|
||||
String id = getJsonProperty(profile, "id");
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@ package org.keycloak.social.github;
|
|||
import org.codehaus.jackson.JsonNode;
|
||||
import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
|
||||
import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
|
||||
import org.keycloak.broker.oidc.util.SimpleHttp;
|
||||
import org.keycloak.broker.oidc.util.JsonSimpleHttp;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.social.SocialIdentityProvider;
|
||||
|
@ -28,7 +29,7 @@ public class GitHubIdentityProvider extends AbstractOAuth2IdentityProvider imple
|
|||
@Override
|
||||
protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
|
||||
try {
|
||||
JsonNode profile = SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken).asJson();
|
||||
JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken));
|
||||
|
||||
BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "id"));
|
||||
|
||||
|
|
|
@ -25,7 +25,8 @@ import org.codehaus.jackson.JsonNode;
|
|||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
|
||||
import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
|
||||
import org.keycloak.broker.oidc.util.SimpleHttp;
|
||||
import org.keycloak.broker.oidc.util.JsonSimpleHttp;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.social.SocialIdentityProvider;
|
||||
|
@ -55,7 +56,7 @@ public class LinkedInIdentityProvider extends AbstractOAuth2IdentityProvider imp
|
|||
protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
|
||||
log.debug("doGetFederatedIdentity()");
|
||||
try {
|
||||
JsonNode profile = SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken).asJson();
|
||||
JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken));
|
||||
|
||||
BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "id"));
|
||||
|
||||
|
|
|
@ -26,7 +26,8 @@ import java.util.HashMap;
|
|||
import org.codehaus.jackson.JsonNode;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
|
||||
import org.keycloak.broker.oidc.util.SimpleHttp;
|
||||
import org.keycloak.broker.oidc.util.JsonSimpleHttp;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.social.SocialIdentityProvider;
|
||||
|
@ -64,7 +65,7 @@ public class StackoverflowIdentityProvider extends AbstractOAuth2IdentityProvide
|
|||
if (log.isDebugEnabled()) {
|
||||
log.debug("StackOverflow profile request to: " + URL);
|
||||
}
|
||||
JsonNode profile = SimpleHttp.doGet(URL).asJson().get("items").get(0);
|
||||
JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(URL)).get("items").get(0);
|
||||
|
||||
BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "user_id"));
|
||||
|
||||
|
|
|
@ -120,7 +120,8 @@
|
|||
"forceAuthn": true,
|
||||
"validateSignature": true,
|
||||
"postBindingResponse": true,
|
||||
"postBindingAuthnRequest": true
|
||||
"postBindingAuthnRequest": true,
|
||||
"backchannelSupported": true
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -169,7 +170,8 @@
|
|||
"tokenUrl": "http://localhost:8082/auth/realms/realm-with-oidc-identity-provider/protocol/openid-connect/token",
|
||||
"userInfoUrl": "http://localhost:8082/auth/realms/realm-with-oidc-identity-provider/protocol/openid-connect/userinfo",
|
||||
"logoutUrl": "http://localhost:8082/auth/realms/realm-with-oidc-identity-provider/tokens/logout",
|
||||
"defaultScope": "email profile"
|
||||
"defaultScope": "email profile",
|
||||
"backchannelSupported": "true"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
Loading…
Reference in a new issue