creds
This commit is contained in:
commit
8e65356891
288 changed files with 7282 additions and 1082 deletions
|
@ -52,7 +52,7 @@ To stop the server press `Ctrl + C`.
|
||||||
|
|
||||||
Help and Documentation
|
Help and Documentation
|
||||||
----------------------
|
----------------------
|
||||||
* [Documentation](http://keycloak.jboss.org/docs) - User Guide, Admin REST API and Javadocs
|
* [Documentation](http://www.keycloak.org/documentation.html) - User Guide, Admin REST API and Javadocs
|
||||||
* [User Mailing List](https://lists.jboss.org/mailman/listinfo/keycloak-user) - Mailing list to ask for help and general questions about Keycloak
|
* [User Mailing List](https://lists.jboss.org/mailman/listinfo/keycloak-user) - Mailing list to ask for help and general questions about Keycloak
|
||||||
* [JIRA](https://issues.jboss.org/projects/KEYCLOAK) - Issue tracker for bugs and feature requests
|
* [JIRA](https://issues.jboss.org/projects/KEYCLOAK) - Issue tracker for bugs and feature requests
|
||||||
|
|
||||||
|
@ -72,4 +72,4 @@ Contributing
|
||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
|
|
||||||
* [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0)
|
* [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
|
|
@ -17,26 +17,21 @@
|
||||||
|
|
||||||
package org.keycloak.adapters;
|
package org.keycloak.adapters;
|
||||||
|
|
||||||
import org.apache.http.HttpEntity;
|
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
import org.apache.http.client.HttpClient;
|
import org.apache.http.client.HttpClient;
|
||||||
import org.apache.http.client.methods.HttpGet;
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
|
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
|
||||||
|
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||||
|
import org.keycloak.adapters.rotation.PublicKeyLocator;
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
import org.keycloak.adapters.spi.HttpFacade;
|
||||||
import org.keycloak.common.enums.RelativeUrlsUsed;
|
import org.keycloak.common.enums.RelativeUrlsUsed;
|
||||||
import org.keycloak.common.enums.SslRequired;
|
import org.keycloak.common.enums.SslRequired;
|
||||||
import org.keycloak.enums.TokenStore;
|
import org.keycloak.enums.TokenStore;
|
||||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||||
import org.keycloak.representations.idm.PublishedRealmRepresentation;
|
|
||||||
import org.keycloak.util.JsonSerialization;
|
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -57,7 +52,7 @@ public class AdapterDeploymentContext {
|
||||||
* during the application deployment's life cycle.
|
* during the application deployment's life cycle.
|
||||||
*
|
*
|
||||||
* @param deployment A KeycloakConfigResolver, possibly missing the Auth
|
* @param deployment A KeycloakConfigResolver, possibly missing the Auth
|
||||||
* Server URL and/or Realm Public Key
|
* Server URL
|
||||||
*/
|
*/
|
||||||
public AdapterDeploymentContext(KeycloakDeployment deployment) {
|
public AdapterDeploymentContext(KeycloakDeployment deployment) {
|
||||||
this.deployment = deployment;
|
this.deployment = deployment;
|
||||||
|
@ -79,7 +74,6 @@ public class AdapterDeploymentContext {
|
||||||
/**
|
/**
|
||||||
* For single-tenant deployments, it complements KeycloakDeployment
|
* For single-tenant deployments, it complements KeycloakDeployment
|
||||||
* by resolving a relative Auth Server's URL based on the current request
|
* by resolving a relative Auth Server's URL based on the current request
|
||||||
* and, if needed, will lazily resolve the Realm's Public Key.
|
|
||||||
*
|
*
|
||||||
* For multi-tenant deployments, defers the resolution of KeycloakDeployment
|
* For multi-tenant deployments, defers the resolution of KeycloakDeployment
|
||||||
* to the KeycloakConfigResolver .
|
* to the KeycloakConfigResolver .
|
||||||
|
@ -98,8 +92,8 @@ public class AdapterDeploymentContext {
|
||||||
if (deployment.getAuthServerBaseUrl() == null) return deployment;
|
if (deployment.getAuthServerBaseUrl() == null) return deployment;
|
||||||
|
|
||||||
KeycloakDeployment resolvedDeployment = resolveUrls(deployment, facade);
|
KeycloakDeployment resolvedDeployment = resolveUrls(deployment, facade);
|
||||||
if (resolvedDeployment.getRealmKey() == null) {
|
if (resolvedDeployment.getPublicKeyLocator() == null) {
|
||||||
resolveRealmKey(resolvedDeployment);
|
throw new RuntimeException("KeycloakDeployment was never initialized through appropriate SPIs");
|
||||||
}
|
}
|
||||||
return resolvedDeployment;
|
return resolvedDeployment;
|
||||||
}
|
}
|
||||||
|
@ -115,45 +109,6 @@ public class AdapterDeploymentContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resolveRealmKey(KeycloakDeployment deployment) {
|
|
||||||
if (deployment.getClient() == null) {
|
|
||||||
throw new RuntimeException("KeycloakDeployment was never initialized through appropriate SPIs");
|
|
||||||
}
|
|
||||||
HttpGet get = new HttpGet(deployment.getRealmInfoUrl());
|
|
||||||
try {
|
|
||||||
HttpResponse response = deployment.getClient().execute(get);
|
|
||||||
int status = response.getStatusLine().getStatusCode();
|
|
||||||
if (status != 200) {
|
|
||||||
close(response);
|
|
||||||
throw new RuntimeException("Unable to resolve realm public key remotely, status = " + status);
|
|
||||||
}
|
|
||||||
HttpEntity entity = response.getEntity();
|
|
||||||
if (entity == null) {
|
|
||||||
throw new RuntimeException("Unable to resolve realm public key remotely. There was no entity.");
|
|
||||||
}
|
|
||||||
InputStream is = entity.getContent();
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
|
||||||
int c;
|
|
||||||
while ((c = is.read()) != -1) {
|
|
||||||
os.write(c);
|
|
||||||
}
|
|
||||||
byte[] bytes = os.toByteArray();
|
|
||||||
String json = new String(bytes);
|
|
||||||
PublishedRealmRepresentation rep = JsonSerialization.readValue(json, PublishedRealmRepresentation.class);
|
|
||||||
deployment.setRealmKey(rep.getPublicKey());
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
is.close();
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("Unable to resolve realm public key remotely", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This delegate is used to store temporary, per-request metadata like request resolved URLs.
|
* This delegate is used to store temporary, per-request metadata like request resolved URLs.
|
||||||
* Ever method is delegated except URL get methods and isConfigured()
|
* Ever method is delegated except URL get methods and isConfigured()
|
||||||
|
@ -207,6 +162,11 @@ public class AdapterDeploymentContext {
|
||||||
return (this.unregisterNodeUrl != null) ? this.unregisterNodeUrl : delegate.getUnregisterNodeUrl();
|
return (this.unregisterNodeUrl != null) ? this.unregisterNodeUrl : delegate.getUnregisterNodeUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getJwksUrl() {
|
||||||
|
return (this.jwksUrl != null) ? this.jwksUrl : delegate.getJwksUrl();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getResourceName() {
|
public String getResourceName() {
|
||||||
return delegate.getResourceName();
|
return delegate.getResourceName();
|
||||||
|
@ -223,13 +183,13 @@ public class AdapterDeploymentContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PublicKey getRealmKey() {
|
public void setPublicKeyLocator(PublicKeyLocator publicKeyLocator) {
|
||||||
return delegate.getRealmKey();
|
delegate.setPublicKeyLocator(publicKeyLocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setRealmKey(PublicKey realmKey) {
|
public PublicKeyLocator getPublicKeyLocator() {
|
||||||
delegate.setRealmKey(realmKey);
|
return delegate.getPublicKeyLocator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -466,6 +426,26 @@ public class AdapterDeploymentContext {
|
||||||
public void setTokenMinimumTimeToLive(final int tokenMinimumTimeToLive) {
|
public void setTokenMinimumTimeToLive(final int tokenMinimumTimeToLive) {
|
||||||
delegate.setTokenMinimumTimeToLive(tokenMinimumTimeToLive);
|
delegate.setTokenMinimumTimeToLive(tokenMinimumTimeToLive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyEnforcer getPolicyEnforcer() {
|
||||||
|
return delegate.getPolicyEnforcer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) {
|
||||||
|
delegate.setPolicyEnforcer(policyEnforcer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) {
|
||||||
|
delegate.setMinTimeBetweenJwksRequests(minTimeBetweenJwksRequests);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMinTimeBetweenJwksRequests() {
|
||||||
|
return delegate.getMinTimeBetweenJwksRequests();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) {
|
protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) {
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.adapters;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.RSATokenVerifier;
|
import org.keycloak.RSATokenVerifier;
|
||||||
|
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||||
import org.keycloak.adapters.spi.AuthChallenge;
|
import org.keycloak.adapters.spi.AuthChallenge;
|
||||||
import org.keycloak.adapters.spi.AuthOutcome;
|
import org.keycloak.adapters.spi.AuthOutcome;
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
import org.keycloak.adapters.spi.HttpFacade;
|
||||||
|
@ -84,7 +85,7 @@ public class BearerTokenRequestAuthenticator {
|
||||||
|
|
||||||
protected AuthOutcome authenticateToken(HttpFacade exchange, String tokenString) {
|
protected AuthOutcome authenticateToken(HttpFacade exchange, String tokenString) {
|
||||||
try {
|
try {
|
||||||
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
|
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
||||||
} catch (VerificationException e) {
|
} catch (VerificationException e) {
|
||||||
log.error("Failed to verify token", e);
|
log.error("Failed to verify token", e);
|
||||||
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "invalid_token", e.getMessage());
|
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "invalid_token", e.getMessage());
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.io.IOException;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.KeycloakPrincipal;
|
import org.keycloak.KeycloakPrincipal;
|
||||||
import org.keycloak.RSATokenVerifier;
|
import org.keycloak.RSATokenVerifier;
|
||||||
|
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
import org.keycloak.adapters.spi.HttpFacade;
|
||||||
import org.keycloak.common.VerificationException;
|
import org.keycloak.common.VerificationException;
|
||||||
import org.keycloak.constants.AdapterConstants;
|
import org.keycloak.constants.AdapterConstants;
|
||||||
|
@ -73,7 +74,7 @@ public class CookieTokenStore {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Skip check if token is active now. It's supposed to be done later by the caller
|
// Skip check if token is active now. It's supposed to be done later by the caller
|
||||||
AccessToken accessToken = RSATokenVerifier.verifyToken(accessTokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl(), false, true);
|
AccessToken accessToken = AdapterRSATokenVerifier.verifyToken(accessTokenString, deployment, false, true);
|
||||||
IDToken idToken;
|
IDToken idToken;
|
||||||
if (idTokenString != null && idTokenString.length() > 0) {
|
if (idTokenString != null && idTokenString.length() > 0) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.adapters;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpRequestBase;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class HttpAdapterUtils {
|
||||||
|
|
||||||
|
|
||||||
|
public static <T> T sendJsonHttpRequest(KeycloakDeployment deployment, HttpRequestBase httpRequest, Class<T> clazz) throws HttpClientAdapterException {
|
||||||
|
try {
|
||||||
|
HttpResponse response = deployment.getClient().execute(httpRequest);
|
||||||
|
int status = response.getStatusLine().getStatusCode();
|
||||||
|
if (status != 200) {
|
||||||
|
close(response);
|
||||||
|
throw new HttpClientAdapterException("Unexpected status = " + status);
|
||||||
|
}
|
||||||
|
HttpEntity entity = response.getEntity();
|
||||||
|
if (entity == null) {
|
||||||
|
throw new HttpClientAdapterException("There was no entity.");
|
||||||
|
}
|
||||||
|
InputStream is = entity.getContent();
|
||||||
|
try {
|
||||||
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||||
|
int c;
|
||||||
|
while ((c = is.read()) != -1) {
|
||||||
|
os.write(c);
|
||||||
|
}
|
||||||
|
byte[] bytes = os.toByteArray();
|
||||||
|
String json = new String(bytes);
|
||||||
|
return JsonSerialization.readValue(json, clazz);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
is.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new HttpClientAdapterException("IO error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void close(HttpResponse response) {
|
||||||
|
if (response.getEntity() != null) {
|
||||||
|
try {
|
||||||
|
response.getEntity().getContent().close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.adapters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class HttpClientAdapterException extends Exception {
|
||||||
|
|
||||||
|
public HttpClientAdapterException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClientAdapterException(String message, Throwable t) {
|
||||||
|
super(message, t);
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ import org.apache.http.client.HttpClient;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
|
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
|
||||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||||
|
import org.keycloak.adapters.rotation.PublicKeyLocator;
|
||||||
import org.keycloak.constants.ServiceUrlConstants;
|
import org.keycloak.constants.ServiceUrlConstants;
|
||||||
import org.keycloak.common.enums.RelativeUrlsUsed;
|
import org.keycloak.common.enums.RelativeUrlsUsed;
|
||||||
import org.keycloak.common.enums.SslRequired;
|
import org.keycloak.common.enums.SslRequired;
|
||||||
|
@ -29,7 +30,6 @@ import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ public class KeycloakDeployment {
|
||||||
|
|
||||||
protected RelativeUrlsUsed relativeUrls;
|
protected RelativeUrlsUsed relativeUrls;
|
||||||
protected String realm;
|
protected String realm;
|
||||||
protected volatile PublicKey realmKey;
|
protected PublicKeyLocator publicKeyLocator;
|
||||||
protected String authServerBaseUrl;
|
protected String authServerBaseUrl;
|
||||||
protected String realmInfoUrl;
|
protected String realmInfoUrl;
|
||||||
protected KeycloakUriBuilder authUrl;
|
protected KeycloakUriBuilder authUrl;
|
||||||
|
@ -52,6 +52,7 @@ public class KeycloakDeployment {
|
||||||
protected String accountUrl;
|
protected String accountUrl;
|
||||||
protected String registerNodeUrl;
|
protected String registerNodeUrl;
|
||||||
protected String unregisterNodeUrl;
|
protected String unregisterNodeUrl;
|
||||||
|
protected String jwksUrl;
|
||||||
protected String principalAttribute = "sub";
|
protected String principalAttribute = "sub";
|
||||||
|
|
||||||
protected String resourceName;
|
protected String resourceName;
|
||||||
|
@ -79,13 +80,14 @@ public class KeycloakDeployment {
|
||||||
|
|
||||||
protected volatile int notBefore;
|
protected volatile int notBefore;
|
||||||
protected int tokenMinimumTimeToLive;
|
protected int tokenMinimumTimeToLive;
|
||||||
|
protected int minTimeBetweenJwksRequests;
|
||||||
private PolicyEnforcer policyEnforcer;
|
private PolicyEnforcer policyEnforcer;
|
||||||
|
|
||||||
public KeycloakDeployment() {
|
public KeycloakDeployment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isConfigured() {
|
public boolean isConfigured() {
|
||||||
return getRealm() != null && getRealmKey() != null && (isBearerOnly() || getAuthServerBaseUrl() != null);
|
return getRealm() != null && getPublicKeyLocator() != null && (isBearerOnly() || getAuthServerBaseUrl() != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getResourceName() {
|
public String getResourceName() {
|
||||||
|
@ -100,12 +102,12 @@ public class KeycloakDeployment {
|
||||||
this.realm = realm;
|
this.realm = realm;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PublicKey getRealmKey() {
|
public PublicKeyLocator getPublicKeyLocator() {
|
||||||
return realmKey;
|
return publicKeyLocator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRealmKey(PublicKey realmKey) {
|
public void setPublicKeyLocator(PublicKeyLocator publicKeyLocator) {
|
||||||
this.realmKey = realmKey;
|
this.publicKeyLocator = publicKeyLocator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAuthServerBaseUrl() {
|
public String getAuthServerBaseUrl() {
|
||||||
|
@ -147,6 +149,7 @@ public class KeycloakDeployment {
|
||||||
accountUrl = authUrlBuilder.clone().path(ServiceUrlConstants.ACCOUNT_SERVICE_PATH).build(getRealm()).toString();
|
accountUrl = authUrlBuilder.clone().path(ServiceUrlConstants.ACCOUNT_SERVICE_PATH).build(getRealm()).toString();
|
||||||
registerNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_REGISTER_NODE_PATH).build(getRealm()).toString();
|
registerNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_REGISTER_NODE_PATH).build(getRealm()).toString();
|
||||||
unregisterNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_UNREGISTER_NODE_PATH).build(getRealm()).toString();
|
unregisterNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_UNREGISTER_NODE_PATH).build(getRealm()).toString();
|
||||||
|
jwksUrl = authUrlBuilder.clone().path(ServiceUrlConstants.JWKS_URL).build(getRealm()).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public RelativeUrlsUsed getRelativeUrls() {
|
public RelativeUrlsUsed getRelativeUrls() {
|
||||||
|
@ -181,6 +184,10 @@ public class KeycloakDeployment {
|
||||||
return unregisterNodeUrl;
|
return unregisterNodeUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getJwksUrl() {
|
||||||
|
return jwksUrl;
|
||||||
|
}
|
||||||
|
|
||||||
public void setResourceName(String resourceName) {
|
public void setResourceName(String resourceName) {
|
||||||
this.resourceName = resourceName;
|
this.resourceName = resourceName;
|
||||||
}
|
}
|
||||||
|
@ -369,6 +376,14 @@ public class KeycloakDeployment {
|
||||||
this.tokenMinimumTimeToLive = tokenMinimumTimeToLive;
|
this.tokenMinimumTimeToLive = tokenMinimumTimeToLive;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getMinTimeBetweenJwksRequests() {
|
||||||
|
return minTimeBetweenJwksRequests;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) {
|
||||||
|
this.minTimeBetweenJwksRequests = minTimeBetweenJwksRequests;
|
||||||
|
}
|
||||||
|
|
||||||
public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) {
|
public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) {
|
||||||
this.policyEnforcer = policyEnforcer;
|
this.policyEnforcer = policyEnforcer;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
||||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||||
|
import org.keycloak.adapters.rotation.HardcodedPublicKeyLocator;
|
||||||
|
import org.keycloak.adapters.rotation.JWKPublicKeyLocator;
|
||||||
import org.keycloak.common.enums.SslRequired;
|
import org.keycloak.common.enums.SslRequired;
|
||||||
import org.keycloak.common.util.PemUtils;
|
import org.keycloak.common.util.PemUtils;
|
||||||
import org.keycloak.enums.TokenStore;
|
import org.keycloak.enums.TokenStore;
|
||||||
|
@ -59,11 +61,16 @@ public class KeycloakDeploymentBuilder {
|
||||||
PublicKey realmKey;
|
PublicKey realmKey;
|
||||||
try {
|
try {
|
||||||
realmKey = PemUtils.decodePublicKey(realmKeyPem);
|
realmKey = PemUtils.decodePublicKey(realmKeyPem);
|
||||||
|
HardcodedPublicKeyLocator pkLocator = new HardcodedPublicKeyLocator(realmKey);
|
||||||
|
deployment.setPublicKeyLocator(pkLocator);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
deployment.setRealmKey(realmKey);
|
} else {
|
||||||
|
JWKPublicKeyLocator pkLocator = new JWKPublicKeyLocator();
|
||||||
|
deployment.setPublicKeyLocator(pkLocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (adapterConfig.getSslRequired() != null) {
|
if (adapterConfig.getSslRequired() != null) {
|
||||||
deployment.setSslRequired(SslRequired.valueOf(adapterConfig.getSslRequired().toUpperCase()));
|
deployment.setSslRequired(SslRequired.valueOf(adapterConfig.getSslRequired().toUpperCase()));
|
||||||
} else {
|
} else {
|
||||||
|
@ -97,6 +104,7 @@ public class KeycloakDeploymentBuilder {
|
||||||
deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup());
|
deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup());
|
||||||
deployment.setRegisterNodePeriod(adapterConfig.getRegisterNodePeriod());
|
deployment.setRegisterNodePeriod(adapterConfig.getRegisterNodePeriod());
|
||||||
deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive());
|
deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive());
|
||||||
|
deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests());
|
||||||
|
|
||||||
if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) {
|
if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) {
|
||||||
throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");
|
throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.adapters;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.RSATokenVerifier;
|
import org.keycloak.RSATokenVerifier;
|
||||||
|
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||||
import org.keycloak.adapters.spi.AdapterSessionStore;
|
import org.keycloak.adapters.spi.AdapterSessionStore;
|
||||||
import org.keycloak.adapters.spi.AuthChallenge;
|
import org.keycloak.adapters.spi.AuthChallenge;
|
||||||
import org.keycloak.adapters.spi.AuthOutcome;
|
import org.keycloak.adapters.spi.AuthOutcome;
|
||||||
|
@ -342,7 +343,7 @@ public class OAuthRequestAuthenticator {
|
||||||
refreshToken = tokenResponse.getRefreshToken();
|
refreshToken = tokenResponse.getRefreshToken();
|
||||||
idTokenString = tokenResponse.getIdToken();
|
idTokenString = tokenResponse.getIdToken();
|
||||||
try {
|
try {
|
||||||
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
|
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
||||||
if (idTokenString != null) {
|
if (idTokenString != null) {
|
||||||
try {
|
try {
|
||||||
JWSInput input = new JWSInput(idTokenString);
|
JWSInput input = new JWSInput(idTokenString);
|
||||||
|
|
|
@ -17,7 +17,10 @@
|
||||||
|
|
||||||
package org.keycloak.adapters;
|
package org.keycloak.adapters;
|
||||||
|
|
||||||
|
import java.security.PublicKey;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
import org.keycloak.adapters.spi.HttpFacade;
|
||||||
import org.keycloak.adapters.spi.UserSessionManagement;
|
import org.keycloak.adapters.spi.UserSessionManagement;
|
||||||
import org.keycloak.jose.jws.JWSInputException;
|
import org.keycloak.jose.jws.JWSInputException;
|
||||||
|
@ -198,7 +201,8 @@ public class PreAuthActionsHandler {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
JWSInput input = new JWSInput(token);
|
JWSInput input = new JWSInput(token);
|
||||||
if (RSAProvider.verify(input, deployment.getRealmKey())) {
|
PublicKey publicKey = AdapterRSATokenVerifier.getPublicKey(input, deployment);
|
||||||
|
if (RSAProvider.verify(input, publicKey)) {
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
} catch (JWSInputException ignore) {
|
} catch (JWSInputException ignore) {
|
||||||
|
|
|
@ -21,6 +21,7 @@ import org.jboss.logging.Logger;
|
||||||
import org.keycloak.AuthorizationContext;
|
import org.keycloak.AuthorizationContext;
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
import org.keycloak.KeycloakSecurityContext;
|
||||||
import org.keycloak.RSATokenVerifier;
|
import org.keycloak.RSATokenVerifier;
|
||||||
|
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||||
import org.keycloak.common.VerificationException;
|
import org.keycloak.common.VerificationException;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
|
@ -130,7 +131,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
|
||||||
String tokenString = response.getToken();
|
String tokenString = response.getToken();
|
||||||
AccessToken token = null;
|
AccessToken token = null;
|
||||||
try {
|
try {
|
||||||
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
|
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
||||||
log.debug("Token Verification succeeded!");
|
log.debug("Token Verification succeeded!");
|
||||||
} catch (VerificationException e) {
|
} catch (VerificationException e) {
|
||||||
log.error("failed verification of token");
|
log.error("failed verification of token");
|
||||||
|
|
|
@ -21,6 +21,7 @@ import org.jboss.logging.Logger;
|
||||||
import org.keycloak.RSATokenVerifier;
|
import org.keycloak.RSATokenVerifier;
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
import org.keycloak.adapters.OIDCHttpFacade;
|
import org.keycloak.adapters.OIDCHttpFacade;
|
||||||
|
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
import org.keycloak.adapters.spi.HttpFacade;
|
||||||
import org.keycloak.authorization.client.AuthorizationDeniedException;
|
import org.keycloak.authorization.client.AuthorizationDeniedException;
|
||||||
import org.keycloak.authorization.client.AuthzClient;
|
import org.keycloak.authorization.client.AuthzClient;
|
||||||
|
@ -120,7 +121,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||||
AuthorizationResponse authzResponse = authzClient.authorization(accessToken).authorize(authzRequest);
|
AuthorizationResponse authzResponse = authzClient.authorization(accessToken).authorize(authzRequest);
|
||||||
|
|
||||||
if (authzResponse != null) {
|
if (authzResponse != null) {
|
||||||
return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl());
|
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -130,7 +131,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||||
|
|
||||||
if (token.getAuthorization() == null) {
|
if (token.getAuthorization() == null) {
|
||||||
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getClientId());
|
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getClientId());
|
||||||
return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl());
|
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
|
||||||
} else {
|
} else {
|
||||||
EntitlementRequest request = new EntitlementRequest();
|
EntitlementRequest request = new EntitlementRequest();
|
||||||
PermissionRequest permissionRequest = new PermissionRequest();
|
PermissionRequest permissionRequest = new PermissionRequest();
|
||||||
|
@ -139,7 +140,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||||
permissionRequest.setScopes(new HashSet<>(pathConfig.getScopes()));
|
permissionRequest.setScopes(new HashSet<>(pathConfig.getScopes()));
|
||||||
request.addPermission(permissionRequest);
|
request.addPermission(permissionRequest);
|
||||||
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).get(authzClient.getConfiguration().getClientId(), request);
|
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).get(authzClient.getConfiguration().getClientId(), request);
|
||||||
return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl());
|
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (AuthorizationDeniedException e) {
|
} catch (AuthorizationDeniedException e) {
|
||||||
|
|
|
@ -43,6 +43,7 @@ import org.keycloak.adapters.AdapterUtils;
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||||
|
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||||
import org.keycloak.common.VerificationException;
|
import org.keycloak.common.VerificationException;
|
||||||
import org.keycloak.common.util.FindFile;
|
import org.keycloak.common.util.FindFile;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
|
@ -88,9 +89,6 @@ public abstract class AbstractKeycloakLoginModule implements LoginModule {
|
||||||
try {
|
try {
|
||||||
InputStream is = FindFile.findFile(keycloakConfigFile);
|
InputStream is = FindFile.findFile(keycloakConfigFile);
|
||||||
KeycloakDeployment kd = KeycloakDeploymentBuilder.build(is);
|
KeycloakDeployment kd = KeycloakDeploymentBuilder.build(is);
|
||||||
if (kd.getRealmKey() == null) {
|
|
||||||
new AdapterDeploymentContext().resolveRealmKey(kd);
|
|
||||||
}
|
|
||||||
return kd;
|
return kd;
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
getLogger().debug("Unable to find or parse file " + keycloakConfigFile + " due to " + e.getMessage(), e);
|
getLogger().debug("Unable to find or parse file " + keycloakConfigFile + " due to " + e.getMessage(), e);
|
||||||
|
@ -190,7 +188,7 @@ public abstract class AbstractKeycloakLoginModule implements LoginModule {
|
||||||
|
|
||||||
|
|
||||||
protected Auth bearerAuth(String tokenString) throws VerificationException {
|
protected Auth bearerAuth(String tokenString) throws VerificationException {
|
||||||
AccessToken token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
|
AccessToken token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
||||||
|
|
||||||
boolean verifyCaller;
|
boolean verifyCaller;
|
||||||
if (deployment.isUseResourceRoleMappings()) {
|
if (deployment.isUseResourceRoleMappings()) {
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.adapters.rotation;
|
||||||
|
|
||||||
|
import java.security.PublicKey;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.RSATokenVerifier;
|
||||||
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
|
import org.keycloak.common.VerificationException;
|
||||||
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
|
import org.keycloak.representations.AccessToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class AdapterRSATokenVerifier {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(AdapterRSATokenVerifier.class);
|
||||||
|
|
||||||
|
public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment) throws VerificationException {
|
||||||
|
return verifyToken(tokenString, deployment, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment) throws VerificationException {
|
||||||
|
PublicKeyLocator pkLocator = deployment.getPublicKeyLocator();
|
||||||
|
|
||||||
|
PublicKey publicKey = pkLocator.getPublicKey(input, deployment);
|
||||||
|
if (publicKey == null) {
|
||||||
|
log.errorf("Didn't find publicKey for kid: %s", input.getHeader().getKeyId());
|
||||||
|
throw new VerificationException("Didn't find publicKey for specified kid");
|
||||||
|
}
|
||||||
|
|
||||||
|
return publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment, boolean checkActive, boolean checkTokenType) throws VerificationException {
|
||||||
|
JWSInput input;
|
||||||
|
try {
|
||||||
|
input = new JWSInput(tokenString);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new VerificationException("Couldn't parse token", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
PublicKey publicKey = getPublicKey(input, deployment);
|
||||||
|
return RSATokenVerifier.verifyToken(input, publicKey, deployment.getRealmInfoUrl(), checkActive, checkTokenType);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.adapters.rotation;
|
||||||
|
|
||||||
|
import java.security.PublicKey;
|
||||||
|
|
||||||
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class HardcodedPublicKeyLocator implements PublicKeyLocator {
|
||||||
|
|
||||||
|
private PublicKey publicKey;
|
||||||
|
|
||||||
|
public HardcodedPublicKeyLocator(PublicKey publicKey) {
|
||||||
|
this.publicKey = publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment) {
|
||||||
|
return publicKey;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.adapters.rotation;
|
||||||
|
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.adapters.HttpAdapterUtils;
|
||||||
|
import org.keycloak.adapters.HttpClientAdapterException;
|
||||||
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.jose.jwk.JSONWebKeySet;
|
||||||
|
import org.keycloak.jose.jwk.JWK;
|
||||||
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
|
import org.keycloak.util.JWKSUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When needed, publicKeys are downloaded by sending request to realm's jwks_url
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class JWKPublicKeyLocator implements PublicKeyLocator {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(JWKPublicKeyLocator.class);
|
||||||
|
|
||||||
|
private Map<String, PublicKey> currentKeys = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private volatile int lastRequestTime = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment) {
|
||||||
|
String kid = input.getHeader().getKeyId();
|
||||||
|
return getPublicKey(kid, deployment);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
|
||||||
|
int minTimeBetweenRequests = deployment.getMinTimeBetweenJwksRequests();
|
||||||
|
|
||||||
|
// Check if key is in cache.
|
||||||
|
PublicKey publicKey = currentKeys.get(kid);
|
||||||
|
if (publicKey != null) {
|
||||||
|
return publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
int currentTime = Time.currentTime();
|
||||||
|
|
||||||
|
// Check if we are allowed to send request
|
||||||
|
if (currentTime > lastRequestTime + minTimeBetweenRequests) {
|
||||||
|
synchronized (this) {
|
||||||
|
currentTime = Time.currentTime();
|
||||||
|
if (currentTime > lastRequestTime + minTimeBetweenRequests) {
|
||||||
|
sendRequest(deployment);
|
||||||
|
lastRequestTime = currentTime;
|
||||||
|
} else {
|
||||||
|
// TODO: debug
|
||||||
|
log.infof("Won't send request to realm jwks url. Last request time was %d", lastRequestTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentKeys.get(kid);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void sendRequest(KeycloakDeployment deployment) {
|
||||||
|
// Send the request
|
||||||
|
// TODO: trace or remove?
|
||||||
|
log.infof("Going to send request to retrieve new set of realm public keys for client %s", deployment.getResourceName());
|
||||||
|
|
||||||
|
HttpGet getMethod = new HttpGet(deployment.getJwksUrl());
|
||||||
|
try {
|
||||||
|
JSONWebKeySet jwks = HttpAdapterUtils.sendJsonHttpRequest(deployment, getMethod, JSONWebKeySet.class);
|
||||||
|
|
||||||
|
Map<String, PublicKey> publicKeys = JWKSUtils.getKeysForUse(jwks, JWK.Use.SIG);
|
||||||
|
|
||||||
|
// TODO: Debug with condition
|
||||||
|
log.infof("Realm public keys successfully retrieved for client %s. New kids: %s", deployment.getResourceName(), publicKeys.keySet().toString());
|
||||||
|
|
||||||
|
// Update current keys
|
||||||
|
currentKeys.clear();
|
||||||
|
currentKeys.putAll(publicKeys);
|
||||||
|
|
||||||
|
} catch (HttpClientAdapterException e) {
|
||||||
|
log.error("Error when sending request to retrieve realm keys", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.adapters.rotation;
|
||||||
|
|
||||||
|
import java.security.PublicKey;
|
||||||
|
|
||||||
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public interface PublicKeyLocator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param input
|
||||||
|
* @param deployment
|
||||||
|
* @return publicKey, which should be used for verify signature on given "input"
|
||||||
|
*/
|
||||||
|
PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment);
|
||||||
|
|
||||||
|
}
|
|
@ -21,6 +21,8 @@ import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.adapters.authentication.ClientIdAndSecretCredentialsProvider;
|
import org.keycloak.adapters.authentication.ClientIdAndSecretCredentialsProvider;
|
||||||
import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
|
import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
|
||||||
|
import org.keycloak.adapters.rotation.HardcodedPublicKeyLocator;
|
||||||
|
import org.keycloak.adapters.rotation.JWKPublicKeyLocator;
|
||||||
import org.keycloak.common.enums.RelativeUrlsUsed;
|
import org.keycloak.common.enums.RelativeUrlsUsed;
|
||||||
import org.keycloak.common.enums.SslRequired;
|
import org.keycloak.common.enums.SslRequired;
|
||||||
import org.keycloak.enums.TokenStore;
|
import org.keycloak.enums.TokenStore;
|
||||||
|
@ -39,7 +41,11 @@ public class KeycloakDeploymentBuilderTest {
|
||||||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak.json"));
|
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak.json"));
|
||||||
assertEquals("demo", deployment.getRealm());
|
assertEquals("demo", deployment.getRealm());
|
||||||
assertEquals("customer-portal", deployment.getResourceName());
|
assertEquals("customer-portal", deployment.getResourceName());
|
||||||
assertEquals(PemUtils.decodePublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"), deployment.getRealmKey());
|
|
||||||
|
assertTrue(deployment.getPublicKeyLocator() instanceof HardcodedPublicKeyLocator);
|
||||||
|
assertEquals(PemUtils.decodePublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"),
|
||||||
|
deployment.getPublicKeyLocator().getPublicKey(null, deployment));
|
||||||
|
|
||||||
assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/auth", deployment.getAuthUrl().build().toString());
|
assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/auth", deployment.getAuthUrl().build().toString());
|
||||||
assertEquals(SslRequired.EXTERNAL, deployment.getSslRequired());
|
assertEquals(SslRequired.EXTERNAL, deployment.getSslRequired());
|
||||||
assertTrue(deployment.isUseResourceRoleMappings());
|
assertTrue(deployment.isUseResourceRoleMappings());
|
||||||
|
@ -62,12 +68,16 @@ public class KeycloakDeploymentBuilderTest {
|
||||||
assertEquals(TokenStore.COOKIE, deployment.getTokenStore());
|
assertEquals(TokenStore.COOKIE, deployment.getTokenStore());
|
||||||
assertEquals("email", deployment.getPrincipalAttribute());
|
assertEquals("email", deployment.getPrincipalAttribute());
|
||||||
assertEquals(10, deployment.getTokenMinimumTimeToLive());
|
assertEquals(10, deployment.getTokenMinimumTimeToLive());
|
||||||
|
assertEquals(20, deployment.getMinTimeBetweenJwksRequests());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void loadNoClientCredentials() throws Exception {
|
public void loadNoClientCredentials() throws Exception {
|
||||||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-no-credentials.json"));
|
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-no-credentials.json"));
|
||||||
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
|
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
|
||||||
|
|
||||||
|
assertTrue(deployment.getPublicKeyLocator() instanceof JWKPublicKeyLocator);
|
||||||
|
assertEquals(10, deployment.getMinTimeBetweenJwksRequests());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"realm": "demo",
|
"realm": "demo",
|
||||||
"resource": "customer-portal",
|
"resource": "customer-portal",
|
||||||
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url": "https://localhost:8443/auth",
|
"auth-server-url": "https://localhost:8443/auth",
|
||||||
"public-client": true,
|
"public-client": true,
|
||||||
"expose-token": true
|
"expose-token": true
|
||||||
|
|
|
@ -29,5 +29,6 @@
|
||||||
"register-node-period": 1000,
|
"register-node-period": 1000,
|
||||||
"token-store": "cookie",
|
"token-store": "cookie",
|
||||||
"principal-attribute": "email",
|
"principal-attribute": "email",
|
||||||
"token-minimum-time-to-live": 10
|
"token-minimum-time-to-live": 10,
|
||||||
|
"min-time-between-jwks-requests": 20
|
||||||
}
|
}
|
|
@ -20,6 +20,7 @@ package org.keycloak.adapters.installed;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.OAuthErrorException;
|
import org.keycloak.OAuthErrorException;
|
||||||
import org.keycloak.RSATokenVerifier;
|
import org.keycloak.RSATokenVerifier;
|
||||||
|
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||||
import org.keycloak.common.VerificationException;
|
import org.keycloak.common.VerificationException;
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||||
|
@ -213,7 +214,7 @@ public class KeycloakInstalled {
|
||||||
refreshToken = tokenResponse.getRefreshToken();
|
refreshToken = tokenResponse.getRefreshToken();
|
||||||
idTokenString = tokenResponse.getIdToken();
|
idTokenString = tokenResponse.getIdToken();
|
||||||
|
|
||||||
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
|
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
||||||
if (idTokenString != null) {
|
if (idTokenString != null) {
|
||||||
try {
|
try {
|
||||||
JWSInput input = new JWSInput(idTokenString);
|
JWSInput input = new JWSInput(idTokenString);
|
||||||
|
|
|
@ -155,7 +155,6 @@
|
||||||
} else if (initOptions) {
|
} else if (initOptions) {
|
||||||
if (initOptions.token || initOptions.refreshToken) {
|
if (initOptions.token || initOptions.refreshToken) {
|
||||||
setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken);
|
setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken);
|
||||||
kc.timeSkew = initOptions.timeSkew || 0;
|
|
||||||
|
|
||||||
if (loginIframe.enable) {
|
if (loginIframe.enable) {
|
||||||
setupCheckLoginIframe().success(function() {
|
setupCheckLoginIframe().success(function() {
|
||||||
|
@ -166,6 +165,8 @@
|
||||||
kc.onAuthError && kc.onAuthError();
|
kc.onAuthError && kc.onAuthError();
|
||||||
if (initOptions.onLoad) {
|
if (initOptions.onLoad) {
|
||||||
onLoad();
|
onLoad();
|
||||||
|
} else {
|
||||||
|
initPromise.setError();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -177,6 +178,8 @@
|
||||||
kc.onAuthError && kc.onAuthError();
|
kc.onAuthError && kc.onAuthError();
|
||||||
if (initOptions.onLoad) {
|
if (initOptions.onLoad) {
|
||||||
onLoad();
|
onLoad();
|
||||||
|
} else {
|
||||||
|
initPromise.setError();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -359,11 +362,10 @@
|
||||||
throw 'Not authenticated';
|
throw 'Not authenticated';
|
||||||
}
|
}
|
||||||
|
|
||||||
var expiresIn = kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew;
|
var expiresIn = kc.tokenParsed['exp'] - Math.ceil(new Date().getTime() / 1000) + kc.timeSkew;
|
||||||
if (minValidity) {
|
if (minValidity) {
|
||||||
expiresIn -= minValidity;
|
expiresIn -= minValidity;
|
||||||
}
|
}
|
||||||
|
|
||||||
return expiresIn < 0;
|
return expiresIn < 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,7 +380,20 @@
|
||||||
minValidity = minValidity || 5;
|
minValidity = minValidity || 5;
|
||||||
|
|
||||||
var exec = function() {
|
var exec = function() {
|
||||||
if (minValidity >= 0 && !kc.isTokenExpired(minValidity)) {
|
var refreshToken = false;
|
||||||
|
if (kc.timeSkew == -1) {
|
||||||
|
console.info('Skew ' + kc.timeSkew);
|
||||||
|
refreshToken = true;
|
||||||
|
console.info('[KEYCLOAK] Refreshing token: time skew not set');
|
||||||
|
} else if (minValidity == -1) {
|
||||||
|
refreshToken = true;
|
||||||
|
console.info('[KEYCLOAK] Refreshing token: forced refresh');
|
||||||
|
} else if (kc.isTokenExpired(minValidity)) {
|
||||||
|
refreshToken = true;
|
||||||
|
console.info('[KEYCLOAK] Refreshing token: token expired');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!refreshToken) {
|
||||||
promise.setSuccess(false);
|
promise.setSuccess(false);
|
||||||
} else {
|
} else {
|
||||||
var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken;
|
var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken;
|
||||||
|
@ -403,18 +418,21 @@
|
||||||
req.onreadystatechange = function () {
|
req.onreadystatechange = function () {
|
||||||
if (req.readyState == 4) {
|
if (req.readyState == 4) {
|
||||||
if (req.status == 200) {
|
if (req.status == 200) {
|
||||||
|
console.info('[KEYCLOAK] Token refreshed');
|
||||||
|
|
||||||
timeLocal = (timeLocal + new Date().getTime()) / 2;
|
timeLocal = (timeLocal + new Date().getTime()) / 2;
|
||||||
|
|
||||||
var tokenResponse = JSON.parse(req.responseText);
|
var tokenResponse = JSON.parse(req.responseText);
|
||||||
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
|
|
||||||
|
|
||||||
setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token']);
|
setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], timeLocal);
|
||||||
|
|
||||||
kc.onAuthRefreshSuccess && kc.onAuthRefreshSuccess();
|
kc.onAuthRefreshSuccess && kc.onAuthRefreshSuccess();
|
||||||
for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
|
for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
|
||||||
p.setSuccess(true);
|
p.setSuccess(true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
console.warn('[KEYCLOAK] Failed to refresh token');
|
||||||
|
|
||||||
kc.onAuthRefreshError && kc.onAuthRefreshError();
|
kc.onAuthRefreshError && kc.onAuthRefreshError();
|
||||||
for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
|
for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
|
||||||
p.setError(true);
|
p.setError(true);
|
||||||
|
@ -525,18 +543,16 @@
|
||||||
function authSuccess(accessToken, refreshToken, idToken, fulfillPromise) {
|
function authSuccess(accessToken, refreshToken, idToken, fulfillPromise) {
|
||||||
timeLocal = (timeLocal + new Date().getTime()) / 2;
|
timeLocal = (timeLocal + new Date().getTime()) / 2;
|
||||||
|
|
||||||
setToken(accessToken, refreshToken, idToken);
|
setToken(accessToken, refreshToken, idToken, timeLocal);
|
||||||
|
|
||||||
if ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) ||
|
if ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) ||
|
||||||
(kc.refreshTokenParsed && kc.refreshTokenParsed.nonce != oauth.storedNonce) ||
|
(kc.refreshTokenParsed && kc.refreshTokenParsed.nonce != oauth.storedNonce) ||
|
||||||
(kc.idTokenParsed && kc.idTokenParsed.nonce != oauth.storedNonce)) {
|
(kc.idTokenParsed && kc.idTokenParsed.nonce != oauth.storedNonce)) {
|
||||||
|
|
||||||
console.log('invalid nonce!');
|
console.info('[KEYCLOAK] Invalid nonce, clearing token');
|
||||||
kc.clearToken();
|
kc.clearToken();
|
||||||
promise && promise.setError();
|
promise && promise.setError();
|
||||||
} else {
|
} else {
|
||||||
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
|
|
||||||
|
|
||||||
if (fulfillPromise) {
|
if (fulfillPromise) {
|
||||||
kc.onAuthSuccess && kc.onAuthSuccess();
|
kc.onAuthSuccess && kc.onAuthSuccess();
|
||||||
promise && promise.setSuccess();
|
promise && promise.setSuccess();
|
||||||
|
@ -609,7 +625,7 @@
|
||||||
return promise.promise;
|
return promise.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setToken(token, refreshToken, idToken) {
|
function setToken(token, refreshToken, idToken, timeLocal) {
|
||||||
if (kc.tokenTimeoutHandle) {
|
if (kc.tokenTimeoutHandle) {
|
||||||
clearTimeout(kc.tokenTimeoutHandle);
|
clearTimeout(kc.tokenTimeoutHandle);
|
||||||
kc.tokenTimeoutHandle = null;
|
kc.tokenTimeoutHandle = null;
|
||||||
|
@ -628,12 +644,23 @@
|
||||||
kc.realmAccess = kc.tokenParsed.realm_access;
|
kc.realmAccess = kc.tokenParsed.realm_access;
|
||||||
kc.resourceAccess = kc.tokenParsed.resource_access;
|
kc.resourceAccess = kc.tokenParsed.resource_access;
|
||||||
|
|
||||||
|
if (timeLocal) {
|
||||||
|
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
|
||||||
|
console.info('[KEYCLOAK] Estimated time difference between browser and server is ' + kc.timeSkew + ' seconds');
|
||||||
|
} else {
|
||||||
|
kc.timeSkew = -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (kc.onTokenExpired) {
|
if (kc.onTokenExpired) {
|
||||||
var expiresIn = (kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew) * 1000;
|
if (kc.timeSkew == -1) {
|
||||||
if (expiresIn <= 0) {
|
|
||||||
kc.onTokenExpired();
|
kc.onTokenExpired();
|
||||||
} else {
|
} else {
|
||||||
kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn);
|
var expiresIn = (kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew) * 1000;
|
||||||
|
if (expiresIn <= 0) {
|
||||||
|
kc.onTokenExpired();
|
||||||
|
} else {
|
||||||
|
kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,5 @@ public class HttpSessionManager implements HttpSessionListener, UserSessionManag
|
||||||
session.invalidate();
|
session.invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sessions.clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
69
common/src/main/java/org/keycloak/common/Profile.java
Executable file
69
common/src/main/java/org/keycloak/common/Profile.java
Executable file
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.common;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class Profile {
|
||||||
|
|
||||||
|
private enum ProfileValue {
|
||||||
|
PRODUCT, PREVIEW, COMMUNITY
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ProfileValue value = load();
|
||||||
|
|
||||||
|
static ProfileValue load() {
|
||||||
|
String profile = null;
|
||||||
|
try {
|
||||||
|
profile = System.getProperty("keycloak.profile");
|
||||||
|
if (profile == null) {
|
||||||
|
String jbossServerConfigDir = System.getProperty("jboss.server.config.dir");
|
||||||
|
if (jbossServerConfigDir != null) {
|
||||||
|
File file = new File(jbossServerConfigDir, "profile.properties");
|
||||||
|
if (file.isFile()) {
|
||||||
|
Properties props = new Properties();
|
||||||
|
props.load(new FileInputStream(file));
|
||||||
|
profile = props.getProperty("profile");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile == null) {
|
||||||
|
return ProfileValue.valueOf(Version.DEFAULT_PROFILE.toUpperCase());
|
||||||
|
} else {
|
||||||
|
return ProfileValue.valueOf(profile.toUpperCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getName() {
|
||||||
|
return value.name().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isPreviewEnabled() {
|
||||||
|
return value.ordinal() >= ProfileValue.PREVIEW.ordinal();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ public class Version {
|
||||||
public static String VERSION;
|
public static String VERSION;
|
||||||
public static String RESOURCES_VERSION;
|
public static String RESOURCES_VERSION;
|
||||||
public static String BUILD_TIME;
|
public static String BUILD_TIME;
|
||||||
|
public static String DEFAULT_PROFILE;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
Properties props = new Properties();
|
Properties props = new Properties();
|
||||||
|
@ -40,6 +41,7 @@ public class Version {
|
||||||
props.load(is);
|
props.load(is);
|
||||||
Version.NAME = props.getProperty("name");
|
Version.NAME = props.getProperty("name");
|
||||||
Version.NAME_HTML = props.getProperty("name-html");
|
Version.NAME_HTML = props.getProperty("name-html");
|
||||||
|
Version.DEFAULT_PROFILE = props.getProperty("default-profile");
|
||||||
Version.VERSION = props.getProperty("version");
|
Version.VERSION = props.getProperty("version");
|
||||||
Version.BUILD_TIME = props.getProperty("build-time");
|
Version.BUILD_TIME = props.getProperty("build-time");
|
||||||
Version.RESOURCES_VERSION = Version.VERSION.toLowerCase();
|
Version.RESOURCES_VERSION = Version.VERSION.toLowerCase();
|
||||||
|
|
|
@ -18,4 +18,5 @@
|
||||||
name=${product.name}
|
name=${product.name}
|
||||||
name-html=${product.name-html}
|
name-html=${product.name-html}
|
||||||
version=${product.version}
|
version=${product.version}
|
||||||
build-time=${product.build-time}
|
build-time=${product.build-time}
|
||||||
|
default-profile=${product.default-profile}
|
|
@ -38,6 +38,12 @@ public class RSATokenVerifier {
|
||||||
public static AccessToken verifyToken(String tokenString, PublicKey realmKey, String realmUrl, boolean checkActive, boolean checkTokenType) throws VerificationException {
|
public static AccessToken verifyToken(String tokenString, PublicKey realmKey, String realmUrl, boolean checkActive, boolean checkTokenType) throws VerificationException {
|
||||||
AccessToken token = toAccessToken(tokenString, realmKey);
|
AccessToken token = toAccessToken(tokenString, realmKey);
|
||||||
|
|
||||||
|
tokenVerifications(token, realmUrl, checkActive, checkTokenType);
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void tokenVerifications(AccessToken token, String realmUrl, boolean checkActive, boolean checkTokenType) throws VerificationException {
|
||||||
String user = token.getSubject();
|
String user = token.getSubject();
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new VerificationException("Token user was null.");
|
throw new VerificationException("Token user was null.");
|
||||||
|
@ -60,9 +66,9 @@ public class RSATokenVerifier {
|
||||||
throw new VerificationException("Token is not active.");
|
throw new VerificationException("Token is not active.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return token;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static AccessToken toAccessToken(String tokenString, PublicKey realmKey) throws VerificationException {
|
public static AccessToken toAccessToken(String tokenString, PublicKey realmKey) throws VerificationException {
|
||||||
JWSInput input;
|
JWSInput input;
|
||||||
try {
|
try {
|
||||||
|
@ -81,6 +87,23 @@ public class RSATokenVerifier {
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static AccessToken verifyToken(JWSInput input, PublicKey realmKey, String realmUrl, boolean checkActive, boolean checkTokenType) throws VerificationException {
|
||||||
|
if (!isPublicKeyValid(input, realmKey)) throw new VerificationException("Invalid token signature.");
|
||||||
|
|
||||||
|
AccessToken token;
|
||||||
|
try {
|
||||||
|
token = input.readJsonContent(AccessToken.class);
|
||||||
|
} catch (JWSInputException e) {
|
||||||
|
throw new VerificationException("Couldn't parse token signature", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenVerifications(token, realmUrl, checkActive, checkTokenType);
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static boolean isPublicKeyValid(JWSInput input, PublicKey realmKey) throws VerificationException {
|
private static boolean isPublicKeyValid(JWSInput input, PublicKey realmKey) throws VerificationException {
|
||||||
try {
|
try {
|
||||||
return RSAProvider.verify(input, realmKey);
|
return RSAProvider.verify(input, realmKey);
|
||||||
|
|
|
@ -30,5 +30,6 @@ public interface ServiceUrlConstants {
|
||||||
public static final String REALM_INFO_PATH = "/realms/{realm-name}";
|
public static final String REALM_INFO_PATH = "/realms/{realm-name}";
|
||||||
public static final String CLIENTS_MANAGEMENT_REGISTER_NODE_PATH = "/realms/{realm-name}/clients-managements/register-node";
|
public static final String CLIENTS_MANAGEMENT_REGISTER_NODE_PATH = "/realms/{realm-name}/clients-managements/register-node";
|
||||||
public static final String CLIENTS_MANAGEMENT_UNREGISTER_NODE_PATH = "/realms/{realm-name}/clients-managements/unregister-node";
|
public static final String CLIENTS_MANAGEMENT_UNREGISTER_NODE_PATH = "/realms/{realm-name}/clients-managements/unregister-node";
|
||||||
|
public static final String JWKS_URL = "/realms/{realm-name}/protocol/openid-connect/certs";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import org.keycloak.common.util.Base64Url;
|
import org.keycloak.common.util.Base64Url;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.security.KeyFactory;
|
import java.security.KeyFactory;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
@ -66,8 +65,8 @@ public class JWKParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
public PublicKey toPublicKey() {
|
public PublicKey toPublicKey() {
|
||||||
String algorithm = jwk.getKeyType();
|
String keyType = jwk.getKeyType();
|
||||||
if (isAlgorithmSupported(algorithm)) {
|
if (isKeyTypeSupported(keyType)) {
|
||||||
BigInteger modulus = new BigInteger(1, Base64Url.decode(jwk.getOtherClaims().get(RSAPublicJWK.MODULUS).toString()));
|
BigInteger modulus = new BigInteger(1, Base64Url.decode(jwk.getOtherClaims().get(RSAPublicJWK.MODULUS).toString()));
|
||||||
BigInteger publicExponent = new BigInteger(1, Base64Url.decode(jwk.getOtherClaims().get(RSAPublicJWK.PUBLIC_EXPONENT).toString()));
|
BigInteger publicExponent = new BigInteger(1, Base64Url.decode(jwk.getOtherClaims().get(RSAPublicJWK.PUBLIC_EXPONENT).toString()));
|
||||||
|
|
||||||
|
@ -77,12 +76,12 @@ public class JWKParser {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("Unsupported algorithm " + algorithm);
|
throw new RuntimeException("Unsupported keyType " + keyType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAlgorithmSupported(String algorithm) {
|
public boolean isKeyTypeSupported(String keyType) {
|
||||||
return RSAPublicJWK.RSA.equals(algorithm);
|
return RSAPublicJWK.RSA.equals(keyType);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||||
"client-keystore", "client-keystore-password", "client-key-password",
|
"client-keystore", "client-keystore-password", "client-key-password",
|
||||||
"always-refresh-token",
|
"always-refresh-token",
|
||||||
"register-node-at-startup", "register-node-period", "token-store", "principal-attribute",
|
"register-node-at-startup", "register-node-period", "token-store", "principal-attribute",
|
||||||
"proxy-url", "turn-off-change-session-id-on-login", "token-minimum-time-to-live",
|
"proxy-url", "turn-off-change-session-id-on-login", "token-minimum-time-to-live", "min-time-between-jwks-requests",
|
||||||
"policy-enforcer"
|
"policy-enforcer"
|
||||||
})
|
})
|
||||||
public class AdapterConfig extends BaseAdapterConfig {
|
public class AdapterConfig extends BaseAdapterConfig {
|
||||||
|
@ -71,6 +71,8 @@ public class AdapterConfig extends BaseAdapterConfig {
|
||||||
protected Boolean turnOffChangeSessionIdOnLogin;
|
protected Boolean turnOffChangeSessionIdOnLogin;
|
||||||
@JsonProperty("token-minimum-time-to-live")
|
@JsonProperty("token-minimum-time-to-live")
|
||||||
protected int tokenMinimumTimeToLive = 0;
|
protected int tokenMinimumTimeToLive = 0;
|
||||||
|
@JsonProperty("min-time-between-jwks-requests")
|
||||||
|
protected int minTimeBetweenJwksRequests = 10;
|
||||||
@JsonProperty("policy-enforcer")
|
@JsonProperty("policy-enforcer")
|
||||||
protected PolicyEnforcerConfig policyEnforcerConfig;
|
protected PolicyEnforcerConfig policyEnforcerConfig;
|
||||||
|
|
||||||
|
@ -216,4 +218,11 @@ public class AdapterConfig extends BaseAdapterConfig {
|
||||||
this.tokenMinimumTimeToLive = tokenMinimumTimeToLive;
|
this.tokenMinimumTimeToLive = tokenMinimumTimeToLive;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getMinTimeBetweenJwksRequests() {
|
||||||
|
return minTimeBetweenJwksRequests;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) {
|
||||||
|
this.minTimeBetweenJwksRequests = minTimeBetweenJwksRequests;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,10 +123,15 @@ public class IdentityProviderRepresentation {
|
||||||
this.updateProfileFirstLoginMode = updateProfileFirstLoginMode;
|
this.updateProfileFirstLoginMode = updateProfileFirstLoginMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Replaced by configuration option in identity provider authenticator
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public boolean isAuthenticateByDefault() {
|
public boolean isAuthenticateByDefault() {
|
||||||
return authenticateByDefault;
|
return authenticateByDefault;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void setAuthenticateByDefault(boolean authenticateByDefault) {
|
public void setAuthenticateByDefault(boolean authenticateByDefault) {
|
||||||
this.authenticateByDefault = authenticateByDefault;
|
this.authenticateByDefault = authenticateByDefault;
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,6 +123,8 @@ public class RealmRepresentation {
|
||||||
protected String resetCredentialsFlow;
|
protected String resetCredentialsFlow;
|
||||||
protected String clientAuthenticationFlow;
|
protected String clientAuthenticationFlow;
|
||||||
|
|
||||||
|
protected Map<String, String> attributes;
|
||||||
|
|
||||||
protected String keycloakVersion;
|
protected String keycloakVersion;
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
|
@ -864,4 +866,12 @@ public class RealmRepresentation {
|
||||||
return identityProviders != null && !identityProviders.isEmpty();
|
return identityProviders != null && !identityProviders.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAttributes(Map<String, String> attributes) {
|
||||||
|
this.attributes = attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getAttributes() {
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,10 +123,12 @@ public class UserRepresentation {
|
||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public Boolean isTotp() {
|
public Boolean isTotp() {
|
||||||
return totp;
|
return totp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void setTotp(Boolean totp) {
|
public void setTotp(Boolean totp) {
|
||||||
this.totp = totp;
|
this.totp = totp;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.representations.info;
|
||||||
|
|
||||||
|
import org.keycloak.common.Profile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class ProfileInfoRepresentation {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private boolean previewEnabled;
|
||||||
|
|
||||||
|
public static ProfileInfoRepresentation create() {
|
||||||
|
ProfileInfoRepresentation info = new ProfileInfoRepresentation();
|
||||||
|
info.setName(Profile.getName());
|
||||||
|
info.setPreviewEnabled(Profile.isPreviewEnabled());
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPreviewEnabled() {
|
||||||
|
return previewEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPreviewEnabled(boolean previewEnabled) {
|
||||||
|
this.previewEnabled = previewEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ public class ServerInfoRepresentation {
|
||||||
|
|
||||||
private SystemInfoRepresentation systemInfo;
|
private SystemInfoRepresentation systemInfo;
|
||||||
private MemoryInfoRepresentation memoryInfo;
|
private MemoryInfoRepresentation memoryInfo;
|
||||||
|
private ProfileInfoRepresentation profileInfo;
|
||||||
|
|
||||||
private Map<String, List<ThemeInfoRepresentation>> themes;
|
private Map<String, List<ThemeInfoRepresentation>> themes;
|
||||||
|
|
||||||
|
@ -66,6 +67,14 @@ public class ServerInfoRepresentation {
|
||||||
this.memoryInfo = memoryInfo;
|
this.memoryInfo = memoryInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ProfileInfoRepresentation getProfileInfo() {
|
||||||
|
return profileInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProfileInfo(ProfileInfoRepresentation profileInfo) {
|
||||||
|
this.profileInfo = profileInfo;
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String, List<ThemeInfoRepresentation>> getThemes() {
|
public Map<String, List<ThemeInfoRepresentation>> getThemes() {
|
||||||
return themes;
|
return themes;
|
||||||
}
|
}
|
||||||
|
|
45
core/src/main/java/org/keycloak/util/JWKSUtils.java
Normal file
45
core/src/main/java/org/keycloak/util/JWKSUtils.java
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.util;
|
||||||
|
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.keycloak.jose.jwk.JSONWebKeySet;
|
||||||
|
import org.keycloak.jose.jwk.JWK;
|
||||||
|
import org.keycloak.jose.jwk.JWKParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class JWKSUtils {
|
||||||
|
|
||||||
|
public static Map<String, PublicKey> getKeysForUse(JSONWebKeySet keySet, JWK.Use requestedUse) {
|
||||||
|
Map<String, PublicKey> result = new HashMap<>();
|
||||||
|
|
||||||
|
for (JWK jwk : keySet.getKeys()) {
|
||||||
|
JWKParser parser = JWKParser.create(jwk);
|
||||||
|
if (jwk.getPublicKeyUse().equals(requestedUse.asString()) && parser.isKeyTypeSupported(jwk.getKeyType())) {
|
||||||
|
result.put(jwk.getKeyId(), parser.toPublicKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,17 +17,6 @@
|
||||||
|
|
||||||
package org.keycloak;
|
package org.keycloak;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonUnwrapped;
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.representations.IDToken;
|
import org.keycloak.representations.IDToken;
|
||||||
|
@ -36,6 +25,13 @@ import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||||
import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
|
@ -138,6 +134,18 @@ public class JsonParserTest {
|
||||||
Assert.assertNull(clientRep.getJwks());
|
Assert.assertNull(clientRep.getJwks());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadOIDCClientRepWithPairwise() throws IOException {
|
||||||
|
String stringRep = "{\"subject_type\": \"pairwise\", \"jwks_uri\": \"https://op.certification.openid.net:60720/export/jwk_60720.json\", \"contacts\": [\"roland.hedberg@umu.se\"], \"application_type\": \"web\", \"grant_types\": [\"authorization_code\"], \"post_logout_redirect_uris\": [\"https://op.certification.openid.net:60720/logout\"], \"redirect_uris\": [\"https://op.certification.openid.net:60720/authz_cb\"], \"response_types\": [\"code\"], \"require_auth_time\": true, \"default_max_age\": 3600}";
|
||||||
|
OIDCClientRepresentation clientRep = JsonSerialization.readValue(stringRep, OIDCClientRepresentation.class);
|
||||||
|
Assert.assertEquals("pairwise", clientRep.getSubjectType());
|
||||||
|
Assert.assertTrue(clientRep.getRequireAuthTime());
|
||||||
|
Assert.assertEquals(3600, clientRep.getDefaultMaxAge().intValue());
|
||||||
|
Assert.assertEquals(1, clientRep.getRedirectUris().size());
|
||||||
|
Assert.assertEquals("https://op.certification.openid.net:60720/authz_cb", clientRep.getRedirectUris().get(0));
|
||||||
|
Assert.assertNull(clientRep.getJwks());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReadOIDCClientRepWithJWKS() throws IOException {
|
public void testReadOIDCClientRepWithJWKS() throws IOException {
|
||||||
String stringRep = "{\"token_endpoint_auth_method\": \"private_key_jwt\", \"subject_type\": \"public\", \"jwks_uri\": null, \"jwks\": {\"keys\": [{\"use\": \"enc\", \"e\": \"AQAB\", \"d\": \"lZQv0_81euRLeUYU84Aodh0ar7ymDlzWP5NMra4Jklkb-lTBWkI-u4RMsPqGYyW3KHRoL_pgzZXSzQx8RLQfER6timRWb--NxMMKllZubByU3RqH2ooNuocJurspYiXkznPW1Mg9DaNXL0C2hwWPQHTeUVISpjgi5TCOV1ccWVyksFruya_VNL1CIByB-L0GL1rqbKv32cDwi2A3_jJa61cpzfLSIBe-lvCO6tuiDsR4qgJnUwnndQFwEI_4mLmD3iNWXrc8N-poleV8mBfMqBB5fWwy_ZTFCpmQ5AywGmctaik_wNhMoWuA4tUfY6_1LdKld-5Cjq55eLtuJjtvuQ\", \"n\": \"tx3Hjdbc19lkTiohbJrNj4jf2_90MEE122CRrwtFu6saDywKcG7Bi7w2FMAK2oTkuWfqhWRb5BEGmnSXdiCEPO5d-ytqP3nwlZXHaCDYscpP8bB4YLhvCn7R8Efw6gwQle24QPRP3lYoFeuUbDUq7GKA5SfaZUvWoeWjqyLIaBspKQsC26_Umx1E4IXLrMSL6nkRnrYcVZBAXrYCeTP1XtsV38_lZVJfHSaJaUy4PKaj3yvgm93EV2CXybPti7CCMXZ34VqqWiF64pQjZsPu3ZTr7ha_TTQq499-zYRQNDvIVsBDLQQIgrbctuGqj6lrXb31Jj3JIEYqH_4h5X9d0Q\", \"q\": \"1q-r-bmMFbIzrLK2U3elksZq8CqUqZxlSfkGMZuVkxgYMS-e4FPzEp2iirG-eO11aa0cpMMoBdTnVdGJ_ZUR93w0lGf9XnQAJqxP7eOsrUoiW4VWlWH4WfOiLgpO-pFtyTz_JksYYaotc_Z3Zy-Szw6a39IDbuYGy1qL-15oQuc\", \"p\": \"2lrYPppRbcQWu4LtWN6tOVUrtCOPv1eLTKTc7q8vCMcem1Ox5QFB7KnUtNZ5Ni7wnZUeVDfimNebtjNsGvDSrpgIlo9dEnFBQsQIkzZ2SkoYfgmF8hNdi6P-BfRjdgYouy4c6xAnGDgSMTip1YnPRyvbMaoYT9E_tEcBW5wOeoc\", \"kid\": \"a0\", \"kty\": \"RSA\"}, {\"use\": \"sig\", \"e\": \"AQAB\", \"d\": \"DodXDEtkovWWGsMEXYy_nEEMCWyROMOebCnCv0ey3i4M4bh2dmwqgz0e-IKQAFlGiMkidGL1lNbq0uFS04FbuRAR06dYw1cbrNbDdhrWFxKTd1L5D9p-x-gW-YDWhpI8rUGRa76JXkOSxZUbg09_QyUd99CXAHh-FXi_ZkIKD8hK6FrAs68qhLf8MNkUv63DTduw7QgeFfQivdopePxyGuMk5n8veqwsUZsklQkhNlTYQqeM1xb2698ZQcNYkl0OssEsSJKRjXt-LRPowKrdvTuTo2p--HMI0pIEeFs7H_u5OW3jihjvoFClGPynHQhgWmQzlQRvWRXh6FhDVqFeGQ\", \"n\": \"zfZzttF7HmnTYwSMPdxKs5AoczbNS2mOPz-tN1g4ljqI_F1DG8cgQDcN_VDufxoFGRERo2FK6WEN41LhbGEyP6uL6wW6Cy29qE9QZcvY5mXrncndRSOkNcMizvuEJes_fMYrmP_lPiC6kWiqItTk9QBWqJfiYKhCx9cSDXsBmJXn3KWQCVHvj1ANFWW0CWLMKlWN-_NMNLIWJN_pEAocTZMzxSFBK1b5_5J8ZS7hfWRF6MQmjsJcz2jzA21SQZNpre3kwnTGRSwo05sAS-TyeadDqQPWgbqX69UzcGq5irhzN8cpZ_JaTk3Y_uV6owanTZLVvCgdjaAnMYeZhb0KFw\", \"q\": \"5E5XKK5njT-zzRqqTeY2tgP9PJBACeaH_xQRHZ_1ydE7tVd7HdgdaEHfQ1jvKIHFkknWWOBAY1mlBc4YDirLShB_voShD8C-Hx3nF5sne5fleVfU-sZy6Za4B2U75PcE62oZgCPauOTAEm9Xuvrt5aMMovyzR8ecJZhm9bw7naU\", \"p\": \"5vJHCSM3H3q4RltYzENC9RyZZV8EUmpkv9moyguT5t-BUGA-T4W_FGIxzOPXRWOckIplKkoDKhavUeNmTZMCUcue0nkICSJpvNE4Nb2p5PZk_QqSdQNvCasQtdojEG0AmfVD85SU551CYxJdLdDFOqyK2entpMr8lhokem189As\", \"kid\": \"a1\", \"kty\": \"RSA\"}, {\"d\": \"S4_OufhLBgXFMgIDMI1zlVe2uCExpcEAQ80J_lXfS8I\", \"use\": \"sig\", \"crv\": \"P-256\", \"kty\": \"EC\", \"y\": \"DBdNyq30mXmUs_BIvKMqaTTNO7HDhCi0YiC8GciwNYk\", \"x\": \"cYwzBoyjRjxj334bRTqanONf7DUYK-6TgiuN0DixJAk\", \"kid\": \"a2\"}, {\"d\": \"33TnYgdJtWAiVosKqUnz0zSmvWTbsx5-6pceynW6Xck\", \"use\": \"enc\", \"crv\": \"P-256\", \"kty\": \"EC\", \"y\": \"Cula95Eix1Ia77St3OULe6-UKWs5I06nmdfUzhXUQTs\", \"x\": \"wk8HBVxNNzj1gJBxPmmx9XYW1L61ObBGzxpRa6_OqWU\", \"kid\": \"a3\"}]}, \"application_type\": \"web\", \"contacts\": [\"roland.hedberg@umu.se\"], \"post_logout_redirect_uris\": [\"https://op.certification.openid.net:60784/logout\"], \"redirect_uris\": [\"https://op.certification.openid.net:60784/authz_cb\"], \"response_types\": [\"code\"], \"require_auth_time\": true, \"grant_types\": [\"authorization_code\"], \"default_max_age\": 3600}";
|
String stringRep = "{\"token_endpoint_auth_method\": \"private_key_jwt\", \"subject_type\": \"public\", \"jwks_uri\": null, \"jwks\": {\"keys\": [{\"use\": \"enc\", \"e\": \"AQAB\", \"d\": \"lZQv0_81euRLeUYU84Aodh0ar7ymDlzWP5NMra4Jklkb-lTBWkI-u4RMsPqGYyW3KHRoL_pgzZXSzQx8RLQfER6timRWb--NxMMKllZubByU3RqH2ooNuocJurspYiXkznPW1Mg9DaNXL0C2hwWPQHTeUVISpjgi5TCOV1ccWVyksFruya_VNL1CIByB-L0GL1rqbKv32cDwi2A3_jJa61cpzfLSIBe-lvCO6tuiDsR4qgJnUwnndQFwEI_4mLmD3iNWXrc8N-poleV8mBfMqBB5fWwy_ZTFCpmQ5AywGmctaik_wNhMoWuA4tUfY6_1LdKld-5Cjq55eLtuJjtvuQ\", \"n\": \"tx3Hjdbc19lkTiohbJrNj4jf2_90MEE122CRrwtFu6saDywKcG7Bi7w2FMAK2oTkuWfqhWRb5BEGmnSXdiCEPO5d-ytqP3nwlZXHaCDYscpP8bB4YLhvCn7R8Efw6gwQle24QPRP3lYoFeuUbDUq7GKA5SfaZUvWoeWjqyLIaBspKQsC26_Umx1E4IXLrMSL6nkRnrYcVZBAXrYCeTP1XtsV38_lZVJfHSaJaUy4PKaj3yvgm93EV2CXybPti7CCMXZ34VqqWiF64pQjZsPu3ZTr7ha_TTQq499-zYRQNDvIVsBDLQQIgrbctuGqj6lrXb31Jj3JIEYqH_4h5X9d0Q\", \"q\": \"1q-r-bmMFbIzrLK2U3elksZq8CqUqZxlSfkGMZuVkxgYMS-e4FPzEp2iirG-eO11aa0cpMMoBdTnVdGJ_ZUR93w0lGf9XnQAJqxP7eOsrUoiW4VWlWH4WfOiLgpO-pFtyTz_JksYYaotc_Z3Zy-Szw6a39IDbuYGy1qL-15oQuc\", \"p\": \"2lrYPppRbcQWu4LtWN6tOVUrtCOPv1eLTKTc7q8vCMcem1Ox5QFB7KnUtNZ5Ni7wnZUeVDfimNebtjNsGvDSrpgIlo9dEnFBQsQIkzZ2SkoYfgmF8hNdi6P-BfRjdgYouy4c6xAnGDgSMTip1YnPRyvbMaoYT9E_tEcBW5wOeoc\", \"kid\": \"a0\", \"kty\": \"RSA\"}, {\"use\": \"sig\", \"e\": \"AQAB\", \"d\": \"DodXDEtkovWWGsMEXYy_nEEMCWyROMOebCnCv0ey3i4M4bh2dmwqgz0e-IKQAFlGiMkidGL1lNbq0uFS04FbuRAR06dYw1cbrNbDdhrWFxKTd1L5D9p-x-gW-YDWhpI8rUGRa76JXkOSxZUbg09_QyUd99CXAHh-FXi_ZkIKD8hK6FrAs68qhLf8MNkUv63DTduw7QgeFfQivdopePxyGuMk5n8veqwsUZsklQkhNlTYQqeM1xb2698ZQcNYkl0OssEsSJKRjXt-LRPowKrdvTuTo2p--HMI0pIEeFs7H_u5OW3jihjvoFClGPynHQhgWmQzlQRvWRXh6FhDVqFeGQ\", \"n\": \"zfZzttF7HmnTYwSMPdxKs5AoczbNS2mOPz-tN1g4ljqI_F1DG8cgQDcN_VDufxoFGRERo2FK6WEN41LhbGEyP6uL6wW6Cy29qE9QZcvY5mXrncndRSOkNcMizvuEJes_fMYrmP_lPiC6kWiqItTk9QBWqJfiYKhCx9cSDXsBmJXn3KWQCVHvj1ANFWW0CWLMKlWN-_NMNLIWJN_pEAocTZMzxSFBK1b5_5J8ZS7hfWRF6MQmjsJcz2jzA21SQZNpre3kwnTGRSwo05sAS-TyeadDqQPWgbqX69UzcGq5irhzN8cpZ_JaTk3Y_uV6owanTZLVvCgdjaAnMYeZhb0KFw\", \"q\": \"5E5XKK5njT-zzRqqTeY2tgP9PJBACeaH_xQRHZ_1ydE7tVd7HdgdaEHfQ1jvKIHFkknWWOBAY1mlBc4YDirLShB_voShD8C-Hx3nF5sne5fleVfU-sZy6Za4B2U75PcE62oZgCPauOTAEm9Xuvrt5aMMovyzR8ecJZhm9bw7naU\", \"p\": \"5vJHCSM3H3q4RltYzENC9RyZZV8EUmpkv9moyguT5t-BUGA-T4W_FGIxzOPXRWOckIplKkoDKhavUeNmTZMCUcue0nkICSJpvNE4Nb2p5PZk_QqSdQNvCasQtdojEG0AmfVD85SU551CYxJdLdDFOqyK2entpMr8lhokem189As\", \"kid\": \"a1\", \"kty\": \"RSA\"}, {\"d\": \"S4_OufhLBgXFMgIDMI1zlVe2uCExpcEAQ80J_lXfS8I\", \"use\": \"sig\", \"crv\": \"P-256\", \"kty\": \"EC\", \"y\": \"DBdNyq30mXmUs_BIvKMqaTTNO7HDhCi0YiC8GciwNYk\", \"x\": \"cYwzBoyjRjxj334bRTqanONf7DUYK-6TgiuN0DixJAk\", \"kid\": \"a2\"}, {\"d\": \"33TnYgdJtWAiVosKqUnz0zSmvWTbsx5-6pceynW6Xck\", \"use\": \"enc\", \"crv\": \"P-256\", \"kty\": \"EC\", \"y\": \"Cula95Eix1Ia77St3OULe6-UKWs5I06nmdfUzhXUQTs\", \"x\": \"wk8HBVxNNzj1gJBxPmmx9XYW1L61ObBGzxpRa6_OqWU\", \"kid\": \"a3\"}]}, \"application_type\": \"web\", \"contacts\": [\"roland.hedberg@umu.se\"], \"post_logout_redirect_uris\": [\"https://op.certification.openid.net:60784/logout\"], \"redirect_uris\": [\"https://op.certification.openid.net:60784/authz_cb\"], \"response_types\": [\"code\"], \"require_auth_time\": true, \"grant_types\": [\"authorization_code\"], \"default_max_age\": 3600}";
|
||||||
|
|
|
@ -112,12 +112,6 @@
|
||||||
<type>zip</type>
|
<type>zip</type>
|
||||||
<destFileName>keycloak-examples-${project.version}.zip</destFileName>
|
<destFileName>keycloak-examples-${project.version}.zip</destFileName>
|
||||||
</artifactItem>
|
</artifactItem>
|
||||||
<artifactItem>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-src-dist</artifactId>
|
|
||||||
<type>zip</type>
|
|
||||||
<destFileName>keycloak-src-${project.version}.zip</destFileName>
|
|
||||||
</artifactItem>
|
|
||||||
</artifactItems>
|
</artifactItems>
|
||||||
<outputDirectory>target/${project.version}</outputDirectory>
|
<outputDirectory>target/${project.version}</outputDirectory>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
@ -40,7 +40,6 @@
|
||||||
<module>proxy-dist</module>
|
<module>proxy-dist</module>
|
||||||
<module>server-dist</module>
|
<module>server-dist</module>
|
||||||
<module>server-overlay</module>
|
<module>server-overlay</module>
|
||||||
<module>src-dist</module>
|
|
||||||
<module>feature-packs</module>
|
<module>feature-packs</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
<!--
|
|
||||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
~ and other contributors as indicated by the @author tags.
|
|
||||||
~
|
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
~ you may not use this file except in compliance with the License.
|
|
||||||
~ You may obtain a copy of the License at
|
|
||||||
~
|
|
||||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
~
|
|
||||||
~ Unless required by applicable law or agreed to in writing, software
|
|
||||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
~ See the License for the specific language governing permissions and
|
|
||||||
~ limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<parent>
|
|
||||||
<artifactId>keycloak-distribution-parent</artifactId>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<version>2.2.0-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>keycloak-src-dist</artifactId>
|
|
||||||
<packaging>pom</packaging>
|
|
||||||
<name>Keycloak Source Distribution</name>
|
|
||||||
<description/>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<finalName>keycloak-src-${project.version}</finalName>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-deploy-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<skip>true</skip>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<artifactId>maven-assembly-plugin</artifactId>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>assemble</id>
|
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>single</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<descriptors>
|
|
||||||
<descriptor>assembly.xml</descriptor>
|
|
||||||
</descriptors>
|
|
||||||
<outputDirectory>
|
|
||||||
target
|
|
||||||
</outputDirectory>
|
|
||||||
<workDirectory>
|
|
||||||
target/assembly/work
|
|
||||||
</workDirectory>
|
|
||||||
<appendAssemblyId>false</appendAssemblyId>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
|
|
||||||
</project>
|
|
|
@ -1,6 +1,5 @@
|
||||||
{
|
{
|
||||||
"realm": "hello-world-authz",
|
"realm": "hello-world-authz",
|
||||||
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url": "http://localhost:8080/auth",
|
"auth-server-url": "http://localhost:8080/auth",
|
||||||
"ssl-required": "external",
|
"ssl-required": "external",
|
||||||
"resource": "hello-world-authz-service",
|
"resource": "hello-world-authz-service",
|
||||||
|
|
|
@ -43,7 +43,7 @@ public class AuthorizationClientExample {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void introspectRequestingPartyToken() {
|
private static void introspectRequestingPartyToken() {
|
||||||
// create a new instance based on the configuration define at keycloak-authz.json
|
// create a new instance based on the configuration defined in keycloak-authz.json
|
||||||
AuthzClient authzClient = AuthzClient.create();
|
AuthzClient authzClient = AuthzClient.create();
|
||||||
|
|
||||||
// query the server for a resource with a given name
|
// query the server for a resource with a given name
|
||||||
|
@ -51,8 +51,9 @@ public class AuthorizationClientExample {
|
||||||
.resource()
|
.resource()
|
||||||
.findByFilter("name=Default Resource");
|
.findByFilter("name=Default Resource");
|
||||||
|
|
||||||
// obtian a Entitlement API Token in order to get access to the Entitlement API.
|
// obtain an Entitlement API Token in order to get access to the Entitlement API.
|
||||||
// this token is just an access token issued to a client on behalf of an user with a scope kc_entitlement
|
// this token is just an access token issued to a client on behalf of an user
|
||||||
|
// with a scope = kc_entitlement
|
||||||
String eat = getEntitlementAPIToken(authzClient);
|
String eat = getEntitlementAPIToken(authzClient);
|
||||||
|
|
||||||
// create an entitlement request
|
// create an entitlement request
|
||||||
|
@ -63,7 +64,8 @@ public class AuthorizationClientExample {
|
||||||
|
|
||||||
request.addPermission(permission);
|
request.addPermission(permission);
|
||||||
|
|
||||||
// send the entitlement request to the server in order to obtain a RPT with all permissions granted to the user
|
// send the entitlement request to the server in order to
|
||||||
|
// obtain an RPT with all permissions granted to the user
|
||||||
EntitlementResponse response = authzClient.entitlement(eat).get("hello-world-authz-service", request);
|
EntitlementResponse response = authzClient.entitlement(eat).get("hello-world-authz-service", request);
|
||||||
String rpt = response.getRpt();
|
String rpt = response.getRpt();
|
||||||
|
|
||||||
|
@ -79,7 +81,7 @@ public class AuthorizationClientExample {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void createResource() {
|
private static void createResource() {
|
||||||
// create a new instance based on the configuration define at keycloak-authz.json
|
// create a new instance based on the configuration defined in keycloak-authz.json
|
||||||
AuthzClient authzClient = AuthzClient.create();
|
AuthzClient authzClient = AuthzClient.create();
|
||||||
|
|
||||||
// create a new resource representation with the information we want
|
// create a new resource representation with the information we want
|
||||||
|
@ -111,8 +113,9 @@ public class AuthorizationClientExample {
|
||||||
// create a new instance based on the configuration define at keycloak-authz.json
|
// create a new instance based on the configuration define at keycloak-authz.json
|
||||||
AuthzClient authzClient = AuthzClient.create();
|
AuthzClient authzClient = AuthzClient.create();
|
||||||
|
|
||||||
// obtian a Entitlement API Token in order to get access to the Entitlement API.
|
// obtain an Entitlement API Token in order to get access to the Entitlement API.
|
||||||
// this token is just an access token issued to a client on behalf of an user with a scope kc_entitlement
|
// this token is just an access token issued to a client on behalf of an user
|
||||||
|
// with a scope = kc_entitlement
|
||||||
String eat = getEntitlementAPIToken(authzClient);
|
String eat = getEntitlementAPIToken(authzClient);
|
||||||
|
|
||||||
// create an entitlement request
|
// create an entitlement request
|
||||||
|
@ -123,7 +126,8 @@ public class AuthorizationClientExample {
|
||||||
|
|
||||||
request.addPermission(permission);
|
request.addPermission(permission);
|
||||||
|
|
||||||
// send the entitlement request to the server in order to obtain a RPT with all permissions granted to the user
|
// send the entitlement request to the server in order to obtain a RPT
|
||||||
|
// with all permissions granted to the user
|
||||||
EntitlementResponse response = authzClient.entitlement(eat).get("hello-world-authz-service", request);
|
EntitlementResponse response = authzClient.entitlement(eat).get("hello-world-authz-service", request);
|
||||||
String rpt = response.getRpt();
|
String rpt = response.getRpt();
|
||||||
|
|
||||||
|
@ -133,7 +137,7 @@ public class AuthorizationClientExample {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void obtainAllEntitlements() {
|
private static void obtainAllEntitlements() {
|
||||||
// create a new instance based on the configuration define at keycloak-authz.json
|
// create a new instance based on the configuration defined in keycloak-authz.json
|
||||||
AuthzClient authzClient = AuthzClient.create();
|
AuthzClient authzClient = AuthzClient.create();
|
||||||
|
|
||||||
// obtian a Entitlement API Token in order to get access to the Entitlement API.
|
// obtian a Entitlement API Token in order to get access to the Entitlement API.
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
{
|
{
|
||||||
"realm": "photoz",
|
"realm": "photoz",
|
||||||
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url" : "http://localhost:8080/auth",
|
"auth-server-url" : "http://localhost:8080/auth",
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"resource" : "photoz-html5-client",
|
"resource" : "photoz-html5-client",
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
{
|
{
|
||||||
"realm": "photoz",
|
"realm": "photoz",
|
||||||
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url": "http://localhost:8080/auth",
|
"auth-server-url": "http://localhost:8080/auth",
|
||||||
"ssl-required": "external",
|
"ssl-required": "external",
|
||||||
"resource": "photoz-restful-api",
|
"resource": "photoz-restful-api",
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
{
|
{
|
||||||
"realm": "servlet-authz",
|
"realm": "servlet-authz",
|
||||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url" : "http://localhost:8080/auth",
|
"auth-server-url" : "http://localhost:8080/auth",
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"resource" : "servlet-authz-app",
|
"resource" : "servlet-authz-app",
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"realm" : "basic-auth",
|
"realm" : "basic-auth",
|
||||||
"resource" : "basic-auth-service",
|
"resource" : "basic-auth-service",
|
||||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url": "/auth",
|
"auth-server-url": "/auth",
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"enable-basic-auth" : "true",
|
"enable-basic-auth" : "true",
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"realm" : "facebook-identity-provider-realm",
|
"realm" : "facebook-identity-provider-realm",
|
||||||
"resource" : "facebook-authentication",
|
"resource" : "facebook-authentication",
|
||||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url": "/auth",
|
"auth-server-url": "/auth",
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"public-client" : true
|
"public-client" : true
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"realm" : "google-identity-provider-realm",
|
"realm" : "google-identity-provider-realm",
|
||||||
"resource" : "google-authentication",
|
"resource" : "google-authentication",
|
||||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url": "/auth",
|
"auth-server-url": "/auth",
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"public-client" : true
|
"public-client" : true
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"realm" : "twitter-identity-provider-realm",
|
"realm" : "twitter-identity-provider-realm",
|
||||||
"resource" : "twitter-authentication",
|
"resource" : "twitter-authentication",
|
||||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url": "/auth",
|
"auth-server-url": "/auth",
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"public-client" : true
|
"public-client" : true
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
{
|
{
|
||||||
"realm" : "cors",
|
"realm" : "cors",
|
||||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url" : "http://localhost-auth:8080/auth",
|
"auth-server-url" : "http://localhost-auth:8080/auth",
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"resource" : "angular-cors-product",
|
"resource" : "angular-cors-product",
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"realm" : "cors",
|
"realm" : "cors",
|
||||||
"resource" : "cors-database-service",
|
"resource" : "cors-database-service",
|
||||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url": "http://localhost-auth:8080/auth",
|
"auth-server-url": "http://localhost-auth:8080/auth",
|
||||||
"bearer-only" : true,
|
"bearer-only" : true,
|
||||||
"ssl-required": "external",
|
"ssl-required": "external",
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
{
|
{
|
||||||
"realm" : "demo",
|
"realm" : "demo",
|
||||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url" : "/auth",
|
"auth-server-url" : "/auth",
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"resource" : "angular-product",
|
"resource" : "angular-product",
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-examples-demo-parent</artifactId>
|
<artifactId>keycloak-examples-demo-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version></version>
|
<version>2.2.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
4
examples/demo-template/angular2-product-app/src/main/webapp/.gitignore
vendored
Normal file
4
examples/demo-template/angular2-product-app/src/main/webapp/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
app/*.js
|
||||||
|
app/*.js.map
|
||||||
|
node_modules
|
||||||
|
typings
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { Http, Headers, RequestOptions, Response } from '@angular/http';
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import 'rxjs/add/operator/map';
|
||||||
|
|
||||||
|
import { KeycloakService } from './keycloak.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-app',
|
||||||
|
template: `
|
||||||
|
<div id="content-area" class="col-md-9" role="main">
|
||||||
|
<div id="content">
|
||||||
|
<h1>Angular2 Product (Beta)</h1>
|
||||||
|
<h2><span>Products</span></h2>
|
||||||
|
<button type="button" (click)="logout()">Sign Out</button>
|
||||||
|
<button type="button" (click)="reloadData()">Reload</button>
|
||||||
|
<table class="table" [hidden]="!products.length">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Product Listing</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let p of products">
|
||||||
|
<td>{{p}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class AppComponent {
|
||||||
|
products: string[] = [];
|
||||||
|
|
||||||
|
constructor(private http: Http, private kc: KeycloakService) {}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
this.kc.logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadData() {
|
||||||
|
//angular dont have http interceptor yet
|
||||||
|
|
||||||
|
this.kc.getToken()
|
||||||
|
.then(token => {
|
||||||
|
let headers = new Headers({
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': 'Bearer ' + token
|
||||||
|
});
|
||||||
|
|
||||||
|
let options = new RequestOptions({ headers });
|
||||||
|
|
||||||
|
this.http.get('/database/products', options)
|
||||||
|
.map(res => res.json())
|
||||||
|
.subscribe(prods => this.products = prods,
|
||||||
|
error => console.log(error));
|
||||||
|
})
|
||||||
|
.catch(error => console.log(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleError(error: Response) {
|
||||||
|
console.error(error);
|
||||||
|
return Observable.throw(error.json().error || 'Server error');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { HttpModule } from '@angular/http';
|
||||||
|
import { KeycloakService } from './keycloak.service';
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
BrowserModule,
|
||||||
|
HttpModule
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
AppComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
KeycloakService,
|
||||||
|
],
|
||||||
|
bootstrap: [ AppComponent ]
|
||||||
|
})
|
||||||
|
export class AppModule {}
|
|
@ -1,78 +0,0 @@
|
||||||
import {Http, Headers,
|
|
||||||
RequestOptions, Response} from 'angular2/http';
|
|
||||||
import {Component} from 'angular2/core';
|
|
||||||
import {Observable} from 'rxjs/Observable';
|
|
||||||
import {KeycloakService} from './keycloak';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'my-app',
|
|
||||||
template:
|
|
||||||
`
|
|
||||||
<div id="content-area" class="col-md-9" role="main">
|
|
||||||
<div id="content">
|
|
||||||
<h1>Angular2 Product (Beta)</h1>
|
|
||||||
<h2><span>Products</span></h2>
|
|
||||||
|
|
||||||
<button type="button" (click)="logout()">Sign Out</button>
|
|
||||||
<button type="button" (click)="reloadData()">Reload</button>
|
|
||||||
<table class="table" [hidden]="!products.length">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Product Listing</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr *ngFor="#p of products">
|
|
||||||
<td>{{p}}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
})
|
|
||||||
export class AppComponent {
|
|
||||||
|
|
||||||
constructor(private _kc:KeycloakService, private http:Http){ }
|
|
||||||
|
|
||||||
products : string[] = [];
|
|
||||||
|
|
||||||
logout(){
|
|
||||||
this._kc.logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
reloadData() {
|
|
||||||
//angular dont have http interceptor yet
|
|
||||||
|
|
||||||
this._kc.getToken().then(
|
|
||||||
token=>{
|
|
||||||
let headers = new Headers({
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Authorization': 'Bearer ' + token
|
|
||||||
});
|
|
||||||
|
|
||||||
let options = new RequestOptions({ headers: headers });
|
|
||||||
|
|
||||||
this.http.get('/database/products', options)
|
|
||||||
.map(res => <string[]> res.json())
|
|
||||||
.subscribe(
|
|
||||||
prods => this.products = prods,
|
|
||||||
error => console.log(error));
|
|
||||||
|
|
||||||
},
|
|
||||||
error=>{
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleError (error: Response) {
|
|
||||||
console.error(error);
|
|
||||||
return Observable.throw(error.json().error || 'Server error');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
declare var Keycloak: any;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class KeycloakService {
|
||||||
|
static auth: any = {};
|
||||||
|
|
||||||
|
static init(): Promise<any> {
|
||||||
|
let keycloakAuth: any = new Keycloak('keycloak.json');
|
||||||
|
KeycloakService.auth.loggedIn = false;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
keycloakAuth.init({ onLoad: 'login-required' })
|
||||||
|
.success(() => {
|
||||||
|
KeycloakService.auth.loggedIn = true;
|
||||||
|
KeycloakService.auth.authz = keycloakAuth;
|
||||||
|
KeycloakService.auth.logoutUrl = keycloakAuth.authServerUrl + "/realms/demo/protocol/openid-connect/logout?redirect_uri=/angular2-product/index.html";
|
||||||
|
resolve();
|
||||||
|
})
|
||||||
|
.error(() => {
|
||||||
|
reject();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
console.log('*** LOGOUT');
|
||||||
|
KeycloakService.auth.loggedIn = false;
|
||||||
|
KeycloakService.auth.authz = null;
|
||||||
|
|
||||||
|
window.location.href = KeycloakService.auth.logoutUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
getToken(): Promise<string> {
|
||||||
|
return new Promise<string>((resolve, reject) => {
|
||||||
|
if (KeycloakService.auth.authz.token) {
|
||||||
|
KeycloakService.auth.authz.updateToken(5)
|
||||||
|
.success(() => {
|
||||||
|
resolve(<string>KeycloakService.auth.authz.token);
|
||||||
|
})
|
||||||
|
.error(() => {
|
||||||
|
reject('Failed to refresh token');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,49 +0,0 @@
|
||||||
import {Injectable} from 'angular2/core';
|
|
||||||
|
|
||||||
|
|
||||||
declare var Keycloak: any;
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class KeycloakService {
|
|
||||||
|
|
||||||
static auth : any = {};
|
|
||||||
|
|
||||||
static init() : Promise<any>{
|
|
||||||
let keycloakAuth : any = new Keycloak('keycloak.json');
|
|
||||||
KeycloakService.auth.loggedIn = false;
|
|
||||||
|
|
||||||
return new Promise((resolve,reject)=>{
|
|
||||||
keycloakAuth.init({ onLoad: 'login-required' })
|
|
||||||
.success( () => {
|
|
||||||
KeycloakService.auth.loggedIn = true;
|
|
||||||
KeycloakService.auth.authz = keycloakAuth;
|
|
||||||
KeycloakService.auth.logoutUrl = keycloakAuth.authServerUrl + "/realms/demo/tokens/logout?redirect_uri=/angular2-product/index.html";
|
|
||||||
resolve(null);
|
|
||||||
})
|
|
||||||
.error(()=> {
|
|
||||||
reject(null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
logout(){
|
|
||||||
console.log('*** LOGOUT');
|
|
||||||
KeycloakService.auth.loggedIn = false;
|
|
||||||
KeycloakService.auth.authz = null;
|
|
||||||
|
|
||||||
window.location.href = KeycloakService.auth.logoutUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
getToken(): Promise<string>{
|
|
||||||
return new Promise<string>((resolve,reject)=>{
|
|
||||||
if (KeycloakService.auth.authz.token) {
|
|
||||||
KeycloakService.auth.authz.updateToken(5).success(function() {
|
|
||||||
resolve(<string>KeycloakService.auth.authz.token);
|
|
||||||
})
|
|
||||||
.error(function() {
|
|
||||||
reject('Failed to refresh token');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +1,11 @@
|
||||||
import 'rxjs/Rx';
|
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||||
import {bootstrap} from 'angular2/platform/browser';
|
import { AppModule } from './app.module';
|
||||||
import {HTTP_BINDINGS} from 'angular2/http';
|
|
||||||
import {KeycloakService} from './keycloak';
|
|
||||||
import {AppComponent} from './app';
|
|
||||||
|
|
||||||
KeycloakService.init().then(
|
import {KeycloakService} from './keycloak.service';
|
||||||
o=>{
|
|
||||||
bootstrap(AppComponent,[HTTP_BINDINGS, KeycloakService]);
|
KeycloakService.init()
|
||||||
},
|
.then(() => {
|
||||||
x=>{
|
const platform = platformBrowserDynamic();
|
||||||
window.location.reload();
|
platform.bootstrapModule(AppModule);
|
||||||
}
|
})
|
||||||
);
|
.catch(() => window.location.reload());
|
||||||
|
|
|
@ -2,48 +2,23 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Angular 2 QuickStart</title>
|
<title>Angular 2 QuickStart</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<!-- 1. Load libraries -->
|
||||||
|
<!-- Polyfill(s) for older browsers -->
|
||||||
|
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||||
|
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||||
|
<script src="node_modules/reflect-metadata/Reflect.js"></script>
|
||||||
|
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||||
|
<script src="/auth/js/keycloak.js"></script>
|
||||||
|
<!-- 2. Configure SystemJS -->
|
||||||
|
<script src="systemjs.config.js"></script>
|
||||||
|
<script>
|
||||||
|
System.import('app').catch(function(err){ console.error(err); });
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<!-- 3. Display the application -->
|
<!-- 3. Display the application -->
|
||||||
<body>
|
<body>
|
||||||
<my-app>Loading...</my-app>
|
<my-app>Loading...</my-app>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- 1. Load libraries -->
|
|
||||||
<!-- IE required polyfills, in this exact order -->
|
|
||||||
<script src="node_modules/es6-shim/es6-shim.min.js"></script>
|
|
||||||
<script src="node_modules/systemjs/dist/system-polyfills.js"></script>
|
|
||||||
|
|
||||||
<script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
|
|
||||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
|
||||||
<script src="node_modules/rxjs/bundles/Rx.js"></script>
|
|
||||||
<script src="node_modules/angular2/bundles/angular2.dev.js"></script>
|
|
||||||
<script src="node_modules/angular2/bundles/http.js"></script>
|
|
||||||
|
|
||||||
|
|
||||||
<script src="/auth/js/keycloak.js"></script>
|
|
||||||
|
|
||||||
<!-- 2. Configure SystemJS -->
|
|
||||||
<script>
|
|
||||||
System.config({
|
|
||||||
packages: {
|
|
||||||
app: {
|
|
||||||
format: 'register',
|
|
||||||
defaultExtension: 'js'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
System.import('app/main')
|
|
||||||
.then(null, console.error.bind(console));
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
{
|
{
|
||||||
"realm": "demo",
|
"realm": "demo",
|
||||||
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url": "/auth",
|
"auth-server-url": "/auth",
|
||||||
"ssl-required": "external",
|
"ssl-required": "external",
|
||||||
"resource": "angular2-product",
|
"resource": "angular2-product",
|
||||||
|
|
|
@ -2,24 +2,36 @@
|
||||||
"name": "angular2-product-app",
|
"name": "angular2-product-app",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite\" ",
|
||||||
|
"lite": "lite-server",
|
||||||
|
"postinstall": "typings install",
|
||||||
"tsc": "tsc",
|
"tsc": "tsc",
|
||||||
"tsc:w": "tsc -w",
|
"tsc:w": "tsc -w",
|
||||||
"lite": "lite-server",
|
"typings": "typings"
|
||||||
"start": "concurrent \"npm run tsc:w\" \"npm run lite\" "
|
|
||||||
},
|
},
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"angular2": "2.0.0-beta.3",
|
"@angular/common": "2.0.0",
|
||||||
"systemjs": "0.19.6",
|
"@angular/compiler": "2.0.0",
|
||||||
"es6-promise": "^3.0.2",
|
"@angular/core": "2.0.0",
|
||||||
"es6-shim": "^0.33.3",
|
"@angular/forms": "2.0.0",
|
||||||
"reflect-metadata": "0.1.2",
|
"@angular/http": "2.0.0",
|
||||||
"rxjs": "5.0.0-beta.0",
|
"@angular/platform-browser": "2.0.0",
|
||||||
"zone.js": "0.5.11"
|
"@angular/platform-browser-dynamic": "2.0.0",
|
||||||
|
"@angular/router": "3.0.0",
|
||||||
|
"@angular/upgrade": "2.0.0",
|
||||||
|
"angular2-in-memory-web-api": "0.0.20",
|
||||||
|
"bootstrap": "^3.3.6",
|
||||||
|
"core-js": "^2.4.1",
|
||||||
|
"reflect-metadata": "^0.1.3",
|
||||||
|
"rxjs": "5.0.0-beta.12",
|
||||||
|
"systemjs": "0.19.27",
|
||||||
|
"zone.js": "^0.6.21"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^1.0.0",
|
"concurrently": "^2.2.0",
|
||||||
"lite-server": "^2.0.1",
|
"lite-server": "^2.2.2",
|
||||||
"typescript": "^1.7.5"
|
"typescript": "^2.0.2",
|
||||||
|
"typings": "^1.3.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
/**
|
||||||
|
* System configuration for Angular 2 samples
|
||||||
|
* Adjust as necessary for your application needs.
|
||||||
|
*/
|
||||||
|
(function (global) {
|
||||||
|
System.config({
|
||||||
|
paths: {
|
||||||
|
// paths serve as alias
|
||||||
|
'npm:': 'node_modules/'
|
||||||
|
},
|
||||||
|
// map tells the System loader where to look for things
|
||||||
|
map: {
|
||||||
|
// our app is within the app folder
|
||||||
|
app: 'app',
|
||||||
|
// angular bundles
|
||||||
|
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
|
||||||
|
'@angular/common': 'npm:@angular/common/bundles/common.umd.js',
|
||||||
|
'@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
|
||||||
|
'@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
|
||||||
|
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
|
||||||
|
'@angular/http': 'npm:@angular/http/bundles/http.umd.js',
|
||||||
|
'@angular/router': 'npm:@angular/router/bundles/router.umd.js',
|
||||||
|
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
|
||||||
|
// other libraries
|
||||||
|
'rxjs': 'npm:rxjs',
|
||||||
|
'angular2-in-memory-web-api': 'npm:angular2-in-memory-web-api',
|
||||||
|
},
|
||||||
|
// packages tells the System loader how to load when no filename and/or no extension
|
||||||
|
packages: {
|
||||||
|
app: {
|
||||||
|
main: './main.js',
|
||||||
|
defaultExtension: 'js'
|
||||||
|
},
|
||||||
|
rxjs: {
|
||||||
|
defaultExtension: 'js'
|
||||||
|
},
|
||||||
|
'angular2-in-memory-web-api': {
|
||||||
|
main: './index.js',
|
||||||
|
defaultExtension: 'js'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})(this);
|
|
@ -1,15 +1,12 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"module": "system",
|
"module": "commonjs",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"sourceMap": false,
|
"sourceMap": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"removeComments": false,
|
"removeComments": false,
|
||||||
"noImplicitAny": false
|
"noImplicitAny": false
|
||||||
},
|
}
|
||||||
"exclude": [
|
}
|
||||||
"node_modules"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"globalDependencies": {
|
||||||
|
"core-js": "registry:dt/core-js#0.0.0+20160725163759",
|
||||||
|
"jasmine": "registry:dt/jasmine#2.2.0+20160621224255",
|
||||||
|
"node": "registry:dt/node#6.0.0+20160909174046"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
{
|
{
|
||||||
"realm" : "demo",
|
"realm" : "demo",
|
||||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url" : "http://localhost:8080/auth",
|
"auth-server-url" : "http://localhost:8080/auth",
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"resource" : "customer-portal-cli",
|
"resource" : "customer-portal-cli",
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"realm": "demo",
|
"realm": "demo",
|
||||||
"resource": "customer-portal-filter",
|
"resource": "customer-portal-filter",
|
||||||
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url": "/auth",
|
"auth-server-url": "/auth",
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"expose-token": true,
|
"expose-token": true,
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
{
|
{
|
||||||
"realm" : "demo",
|
"realm" : "demo",
|
||||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url" : "/auth",
|
"auth-server-url" : "/auth",
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"resource" : "customer-portal-js",
|
"resource" : "customer-portal-js",
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"realm": "demo",
|
"realm": "demo",
|
||||||
"resource": "customer-portal",
|
"resource": "customer-portal",
|
||||||
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url": "/auth",
|
"auth-server-url": "/auth",
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"expose-token": true,
|
"expose-token": true,
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"realm" : "demo",
|
"realm" : "demo",
|
||||||
"resource" : "database-service",
|
"resource" : "database-service",
|
||||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url": "/auth",
|
"auth-server-url": "/auth",
|
||||||
"bearer-only" : true,
|
"bearer-only" : true,
|
||||||
"ssl-required" : "external"
|
"ssl-required" : "external"
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"realm": "demo",
|
"realm": "demo",
|
||||||
"resource": "offline-access-portal",
|
"resource": "offline-access-portal",
|
||||||
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url": "/auth",
|
"auth-server-url": "/auth",
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"credentials": {
|
"credentials": {
|
||||||
|
|
|
@ -51,6 +51,7 @@
|
||||||
<module>example-ear</module>
|
<module>example-ear</module>
|
||||||
<module>admin-access-app</module>
|
<module>admin-access-app</module>
|
||||||
<module>angular-product-app</module>
|
<module>angular-product-app</module>
|
||||||
|
<module>angular2-product-app</module>
|
||||||
<module>database-service</module>
|
<module>database-service</module>
|
||||||
<module>third-party</module>
|
<module>third-party</module>
|
||||||
<module>third-party-cdi</module>
|
<module>third-party-cdi</module>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"realm" : "demo",
|
"realm" : "demo",
|
||||||
"resource" : "product-portal",
|
"resource" : "product-portal",
|
||||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url" : "/auth",
|
"auth-server-url" : "/auth",
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"credentials" : {
|
"credentials" : {
|
||||||
|
|
|
@ -41,6 +41,7 @@ import org.apache.http.impl.client.DefaultHttpClient;
|
||||||
import org.apache.http.message.BasicNameValuePair;
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.RSATokenVerifier;
|
import org.keycloak.RSATokenVerifier;
|
||||||
|
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||||
import org.keycloak.common.VerificationException;
|
import org.keycloak.common.VerificationException;
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||||
|
@ -163,7 +164,7 @@ public abstract class ProductServiceAccountServlet extends HttpServlet {
|
||||||
private void setTokens(HttpServletRequest req, KeycloakDeployment deployment, AccessTokenResponse tokenResponse) throws IOException, VerificationException {
|
private void setTokens(HttpServletRequest req, KeycloakDeployment deployment, AccessTokenResponse tokenResponse) throws IOException, VerificationException {
|
||||||
String token = tokenResponse.getToken();
|
String token = tokenResponse.getToken();
|
||||||
String refreshToken = tokenResponse.getRefreshToken();
|
String refreshToken = tokenResponse.getRefreshToken();
|
||||||
AccessToken tokenParsed = RSATokenVerifier.verifyToken(token, deployment.getRealmKey(), deployment.getRealmInfoUrl());
|
AccessToken tokenParsed = AdapterRSATokenVerifier.verifyToken(token, deployment);
|
||||||
req.getSession().setAttribute(TOKEN, token);
|
req.getSession().setAttribute(TOKEN, token);
|
||||||
req.getSession().setAttribute(REFRESH_TOKEN, refreshToken);
|
req.getSession().setAttribute(REFRESH_TOKEN, refreshToken);
|
||||||
req.getSession().setAttribute(TOKEN_PARSED, tokenParsed);
|
req.getSession().setAttribute(TOKEN_PARSED, tokenParsed);
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
{
|
{
|
||||||
"realm" : "demo",
|
"realm" : "demo",
|
||||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url" : "http://localhost:8080/auth",
|
"auth-server-url" : "http://localhost:8080/auth",
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"resource" : "product-sa-client",
|
"resource" : "product-sa-client",
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
{
|
{
|
||||||
"realm" : "demo",
|
"realm" : "demo",
|
||||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url" : "http://localhost:8080/auth",
|
"auth-server-url" : "http://localhost:8080/auth",
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"resource" : "product-sa-client-jwt-auth",
|
"resource" : "product-sa-client-jwt-auth",
|
||||||
|
|
|
@ -26,7 +26,6 @@
|
||||||
<bean id="kcAdapterConfig" class="org.keycloak.representations.adapters.config.AdapterConfig">
|
<bean id="kcAdapterConfig" class="org.keycloak.representations.adapters.config.AdapterConfig">
|
||||||
<property name="realm" value="demo"/>
|
<property name="realm" value="demo"/>
|
||||||
<property name="resource" value="admin-camel-endpoint"/>
|
<property name="resource" value="admin-camel-endpoint"/>
|
||||||
<property name="realmKey" value="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"/>
|
|
||||||
<property name="bearerOnly" value="true"/>
|
<property name="bearerOnly" value="true"/>
|
||||||
<property name="authServerUrl" value="http://localhost:8080/auth" />
|
<property name="authServerUrl" value="http://localhost:8080/auth" />
|
||||||
<property name="sslRequired" value="EXTERNAL"/>
|
<property name="sslRequired" value="EXTERNAL"/>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"realm": "demo",
|
"realm": "demo",
|
||||||
"resource": "customer-portal",
|
"resource": "customer-portal",
|
||||||
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url": "http://localhost:8080/auth",
|
"auth-server-url": "http://localhost:8080/auth",
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"credentials": {
|
"credentials": {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"realm": "demo",
|
"realm": "demo",
|
||||||
"resource": "builtin-cxf-app",
|
"resource": "builtin-cxf-app",
|
||||||
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url": "http://localhost:8080/auth",
|
"auth-server-url": "http://localhost:8080/auth",
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"credentials": {
|
"credentials": {
|
||||||
|
|
|
@ -32,7 +32,6 @@
|
||||||
<bean id="kcAdapterConfig" class="org.keycloak.representations.adapters.config.AdapterConfig">
|
<bean id="kcAdapterConfig" class="org.keycloak.representations.adapters.config.AdapterConfig">
|
||||||
<property name="realm" value="demo"/>
|
<property name="realm" value="demo"/>
|
||||||
<property name="resource" value="custom-cxf-endpoint"/>
|
<property name="resource" value="custom-cxf-endpoint"/>
|
||||||
<property name="realmKey" value="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"/>
|
|
||||||
<property name="bearerOnly" value="true"/>
|
<property name="bearerOnly" value="true"/>
|
||||||
<property name="authServerUrl" value="http://localhost:8080/auth" />
|
<property name="authServerUrl" value="http://localhost:8080/auth" />
|
||||||
<property name="sslRequired" value="EXTERNAL"/>
|
<property name="sslRequired" value="EXTERNAL"/>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"realm": "demo",
|
"realm": "demo",
|
||||||
"resource": "external-config",
|
"resource": "external-config",
|
||||||
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url": "http://localhost:8080/auth",
|
"auth-server-url": "http://localhost:8080/auth",
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"credentials": {
|
"credentials": {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"realm": "demo",
|
"realm": "demo",
|
||||||
"resource": "ssh-jmx-admin-client",
|
"resource": "ssh-jmx-admin-client",
|
||||||
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"auth-server-url" : "http://localhost:8080/auth",
|
"auth-server-url" : "http://localhost:8080/auth",
|
||||||
"credentials": {
|
"credentials": {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"realm": "demo",
|
"realm": "demo",
|
||||||
"resource": "product-portal",
|
"resource": "product-portal",
|
||||||
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url": "http://localhost:8080/auth",
|
"auth-server-url": "http://localhost:8080/auth",
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"credentials": {
|
"credentials": {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
{
|
{
|
||||||
"realm" : "example",
|
"realm" : "example",
|
||||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url" : "/auth",
|
"auth-server-url" : "/auth",
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"resource" : "js-console",
|
"resource" : "js-console",
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"realm" : "kerberos-demo",
|
"realm" : "kerberos-demo",
|
||||||
"resource" : "kerberos-app",
|
"resource" : "kerberos-app",
|
||||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url": "/auth",
|
"auth-server-url": "/auth",
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"credentials": {
|
"credentials": {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"realm" : "ldap-demo",
|
"realm" : "ldap-demo",
|
||||||
"resource" : "ldap-app",
|
"resource" : "ldap-app",
|
||||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url": "/auth",
|
"auth-server-url": "/auth",
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"credentials": {
|
"credentials": {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"realm" : "tenant1",
|
"realm" : "tenant1",
|
||||||
"resource" : "multi-tenant",
|
"resource" : "multi-tenant",
|
||||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url" : "http://localhost:8080/auth",
|
"auth-server-url" : "http://localhost:8080/auth",
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"credentials" : {
|
"credentials" : {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"realm" : "tenant2",
|
"realm" : "tenant2",
|
||||||
"resource" : "multi-tenant",
|
"resource" : "multi-tenant",
|
||||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDA0oJjgPQJhnVhOo51KauQGfLLreMFu64OJdKXRnfvAQJQTuKNwc5JrR63l/byyW1B6FgclABF818TtLvMCAkn4EuFwQZCZhg3x3+lFGiB/IzC6UAt4Bi0JQrTbdh83/U97GIPegvaDqiqEiQESEkbCZWxM6sh/34hQaAhCaFpMwIDAQAB",
|
|
||||||
"auth-server-url" : "http://localhost:8080/auth",
|
"auth-server-url" : "http://localhost:8080/auth",
|
||||||
"ssl-required" : "external",
|
"ssl-required" : "external",
|
||||||
"credentials" : {
|
"credentials" : {
|
||||||
|
|
|
@ -6,13 +6,12 @@ of Keycloak. To deploy, build this directory then take the jar and copy it to
|
||||||
|
|
||||||
KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.secret-question --resources=target/authenticator-required-action-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi,org.keycloak.keycloak-services,org.jboss.resteasy.resteasy-jaxrs,javax.ws.rs.api"
|
KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.secret-question --resources=target/authenticator-required-action-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi,org.keycloak.keycloak-services,org.jboss.resteasy.resteasy-jaxrs,javax.ws.rs.api"
|
||||||
|
|
||||||
Then registering the provider by editing keycloak-server.json and adding the module to the providers field:
|
Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element:
|
||||||
|
|
||||||
"providers": [
|
|
||||||
....
|
|
||||||
"module:org.keycloak.examples.secret-question"
|
|
||||||
],
|
|
||||||
|
|
||||||
|
<providers>
|
||||||
|
...
|
||||||
|
<provider>module:org.keycloak.examples.secret-question</provider>
|
||||||
|
</providers>
|
||||||
|
|
||||||
You then have to copy the secret-question.ftl and secret-question-config.ftl files to the themes/base/login directory.
|
You then have to copy the secret-question.ftl and secret-question-config.ftl files to the themes/base/login directory.
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ public class SecretQuestionCredentialProvider implements CredentialProvider, Cre
|
||||||
CredentialModel secret = new CredentialModel();
|
CredentialModel secret = new CredentialModel();
|
||||||
secret.setType(SECRET_QUESTION);
|
secret.setType(SECRET_QUESTION);
|
||||||
secret.setValue(credInput.getValue());
|
secret.setValue(credInput.getValue());
|
||||||
secret.setCreatedDate(Time.toMillis(Time.currentTime()));
|
secret.setCreatedDate(Time.currentTimeMillis());
|
||||||
session.userCredentialManager().createCredential(realm ,user, secret);
|
session.userCredentialManager().createCredential(realm ,user, secret);
|
||||||
} else {
|
} else {
|
||||||
creds.get(0).setValue(credInput.getValue());
|
creds.get(0).setValue(credInput.getValue());
|
||||||
|
|
|
@ -6,12 +6,12 @@ To run, deploy as a module by running:
|
||||||
$KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.domain-extension-example --resources=target/domain-extension-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-services,org.keycloak.keycloak-model-jpa,org.keycloak.keycloak-server-spi,javax.ws.rs.api,javax.persistence.api,org.hibernate,org.javassist"
|
$KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.domain-extension-example --resources=target/domain-extension-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-services,org.keycloak.keycloak-model-jpa,org.keycloak.keycloak-server-spi,javax.ws.rs.api,javax.persistence.api,org.hibernate,org.javassist"
|
||||||
|
|
||||||
|
|
||||||
Then registering the provider by editing keycloak-server.json and adding the module to the providers field:
|
Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element:
|
||||||
|
|
||||||
"providers": [
|
<providers>
|
||||||
....
|
...
|
||||||
"module:org.keycloak.examples.domain-extension-example"
|
<provider>module:org.keycloak.examples.domain-extension-example</provider>
|
||||||
],
|
</providers>
|
||||||
|
|
||||||
Then start (or restart) the server.
|
Then start (or restart) the server.
|
||||||
|
|
||||||
|
|
|
@ -5,23 +5,25 @@ To deploy copy target/event-listener-sysout-example.jar to providers directory.
|
||||||
|
|
||||||
KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.event-sysout --resources=target/event-listener-sysout-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi"
|
KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.event-sysout --resources=target/event-listener-sysout-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi"
|
||||||
|
|
||||||
Then registering the provider by editing keycloak-server.json and adding the module to the providers field:
|
Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element:
|
||||||
|
|
||||||
"providers": [
|
<providers>
|
||||||
....
|
...
|
||||||
"module:org.keycloak.examples.event-sysout"
|
<provider>module:org.keycloak.examples.event-sysout</provider>
|
||||||
],
|
</providers>
|
||||||
|
|
||||||
Then start (or restart) the server. Once started open the admin console, select your realm, then click on Events,
|
Then start (or restart) the server. Once started open the admin console, select your realm, then click on Events,
|
||||||
followed by config. Click on Listeners select box, then pick sysout from the dropdown. After this try to logout and
|
followed by config. Click on Listeners select box, then pick sysout from the dropdown. After this try to logout and
|
||||||
login again to see events printed to System.out.
|
login again to see events printed to System.out.
|
||||||
|
|
||||||
The example event listener can be configured to exclude certain events, for example to exclude REFRESH_TOKEN and
|
The example event listener can be configured to exclude certain events, for example to exclude REFRESH_TOKEN and
|
||||||
CODE_TO_TOKEN events add the following to keycloak-server.json:
|
CODE_TO_TOKEN events add the following to `standalone.xml`:
|
||||||
|
|
||||||
...
|
...
|
||||||
"eventsListener": {
|
<spi name="eventsListener">
|
||||||
"sysout": {
|
<provider name="sysout">
|
||||||
"exclude": [ "REFRESH_TOKEN", "CODE_TO_TOKEN" ]
|
<properties>
|
||||||
}
|
<property name="exclude-events" value="["REFRESH_TOKEN", "CODE_TO_TOKEN"]"/>
|
||||||
}
|
</properties>
|
||||||
|
</provider
|
||||||
|
</spi>
|
||||||
|
|
|
@ -5,24 +5,24 @@ To deploy copy target/event-store-mem-example.jar to providers directory. Altern
|
||||||
|
|
||||||
KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.event-inmem --resources=target/event-store-mem-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi"
|
KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.event-inmem --resources=target/event-store-mem-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi"
|
||||||
|
|
||||||
Then registering the provider by editing keycloak-server.json and adding the module to the providers field:
|
Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element:
|
||||||
|
|
||||||
"providers": [
|
<providers>
|
||||||
....
|
...
|
||||||
"module:org.keycloak.examples.event-inmem"
|
<provider>module:org.keycloak.examples.event-inmem</provider>
|
||||||
],
|
</providers>
|
||||||
|
|
||||||
Then edit standalone/configuration/keycloak-server.json, change:
|
Then edit `standalone/configuration/standalone.xml`, change:
|
||||||
|
|
||||||
"eventsStore": {
|
<spi name="eventsStore">
|
||||||
"provider": "jpa"
|
<default-provider>jpa</default-provider>
|
||||||
}
|
</spi>
|
||||||
|
|
||||||
to:
|
to:
|
||||||
|
|
||||||
"eventsStore": {
|
<spi name="eventsStore">
|
||||||
"provider": "in-mem"
|
<default-provider>in-mem</default-provider>
|
||||||
}
|
</spi>
|
||||||
|
|
||||||
Then start (or restart)the server. Once started open the admin console, select your realm, then click on Events,
|
Then start (or restart)the server. Once started open the admin console, select your realm, then click on Events,
|
||||||
followed by config. Set the toggle for Enabled to ON. After this try to logout and login again then open the Events tab
|
followed by config. Set the toggle for Enabled to ON. After this try to logout and login again then open the Events tab
|
||||||
|
|
|
@ -6,13 +6,12 @@ key pairs. To deploy, build this directory then take the jar and copy it to pro
|
||||||
|
|
||||||
KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.userprops --resources=target/federation-properties-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi"
|
KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.userprops --resources=target/federation-properties-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi"
|
||||||
|
|
||||||
Then registering the provider by editing keycloak-server.json and adding the module to the providers field:
|
Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element:
|
||||||
|
|
||||||
"providers": [
|
|
||||||
....
|
|
||||||
"module:org.keycloak.examples.userprops"
|
|
||||||
],
|
|
||||||
|
|
||||||
|
<providers>
|
||||||
|
...
|
||||||
|
<provider>module:org.keycloak.examples.userprops</provider>
|
||||||
|
</providers>
|
||||||
|
|
||||||
You will then have to restart the authentication server.
|
You will then have to restart the authentication server.
|
||||||
|
|
||||||
|
|
|
@ -96,7 +96,7 @@ public abstract class BasePropertiesFederationFactory implements UserFederationP
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* You can import additional plugin configuration from keycloak-server.json here.
|
* You can import additional plugin configuration from standalone.xml here.
|
||||||
*
|
*
|
||||||
* @param config
|
* @param config
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -5,12 +5,12 @@ To deploy copy target/hello-rest-example.jar to providers directory. Alternative
|
||||||
|
|
||||||
$KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.hello-rest-example --resources=target/hello-rest-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi,javax.ws.rs.api"
|
$KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.hello-rest-example --resources=target/hello-rest-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi,javax.ws.rs.api"
|
||||||
|
|
||||||
Then registering the provider by editing keycloak-server.json and adding the module to the providers field:
|
Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element:
|
||||||
|
|
||||||
"providers": [
|
<providers>
|
||||||
....
|
...
|
||||||
"module:org.keycloak.examples.hello-rest-example"
|
<provider>module:org.keycloak.examples.hello-rest-example</provider>
|
||||||
],
|
</providers>
|
||||||
|
|
||||||
Then start (or restart) the server. Once started open http://localhost:8080/realms/master/hello and you should see the message _Hello master_.
|
Then start (or restart) the server. Once started open http://localhost:8080/realms/master/hello and you should see the message _Hello master_.
|
||||||
You can also invoke the endpoint for other realms by replacing `master` with the realm name in the above url.
|
You can also invoke the endpoint for other realms by replacing `master` with the realm name in the above url.
|
|
@ -17,14 +17,14 @@ Alternatively you can deploy as modules. This can be done by first running:
|
||||||
mvn clean install
|
mvn clean install
|
||||||
$KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.example.themes --resources=target/keycloak-example-themes.jar"
|
$KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.example.themes --resources=target/keycloak-example-themes.jar"
|
||||||
|
|
||||||
Then open $KEYCLOAK_HOME/standalone/configuration/keycloak-server.json and register the theme module by adding:
|
Then open `standalone/configuration/standalone.xml` and register the theme module by adding:
|
||||||
|
|
||||||
"theme": {
|
|
||||||
"module": {
|
|
||||||
"modules": [ "org.keycloak.example.themes" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
<theme>
|
||||||
|
...
|
||||||
|
<modules>
|
||||||
|
<module>org.keycloak.example.themes</module>
|
||||||
|
</modules>
|
||||||
|
</theme>
|
||||||
|
|
||||||
Address Theme
|
Address Theme
|
||||||
-------------------
|
-------------------
|
||||||
|
@ -45,11 +45,11 @@ Change Logo Theme
|
||||||
|
|
||||||
To enable the theme open the admin console, select your realm, click on `Theme`. In the dropdowns for `Login Theme`, `Account Theme` and `Admin Console Theme` select `logo-example`. Click `Save` and login to the realm to see the new theme in action.
|
To enable the theme open the admin console, select your realm, click on `Theme`. In the dropdowns for `Login Theme`, `Account Theme` and `Admin Console Theme` select `logo-example`. Click `Save` and login to the realm to see the new theme in action.
|
||||||
|
|
||||||
To change the theme for the welcome pages open `standalone/configuration/keycloak-server.json` find the config for `theme` and add 'welcomeTheme':
|
To change the theme for the welcome pages open `standalone/configuration/standalone.xml` find the config for `theme` and add 'welcomeTheme':
|
||||||
|
|
||||||
"theme": {
|
<theme>
|
||||||
...
|
...
|
||||||
"welcomeTheme": "logo-example"
|
<welcomeTheme>logo-example</welcomeTheme>
|
||||||
},
|
</theme>
|
||||||
|
|
||||||
One thing to note is that to change the admin console for the master admin console (`/auth/admin`) you need to change the theme for the master realm. Changing the admin console theme for any other realms will only change the admin console for that specific realm (for example `/auth/admin/myrealm/console`).
|
One thing to note is that to change the admin console for the master admin console (`/auth/admin`) you need to change the theme for the master realm. Changing the admin console theme for any other realms will only change the admin console for that specific realm (for example `/auth/admin/myrealm/console`).
|
||||||
|
|
|
@ -23,6 +23,20 @@
|
||||||
<target>${maven.compiler.target}</target>
|
<target>${maven.compiler.target}</target>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>2.3</version>
|
||||||
|
<executions>
|
||||||
|
<!-- Run shade goal on package phase -->
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
|
|
@ -16,22 +16,17 @@
|
||||||
*/
|
*/
|
||||||
package cx.ath.matthew;
|
package cx.ath.matthew;
|
||||||
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bruno@abstractj.org">Bruno Oliveira</a>.
|
* @author <a href="mailto:bruno@abstractj.org">Bruno Oliveira</a>.
|
||||||
*/
|
*/
|
||||||
public class LibraryLoader {
|
public class LibraryLoader {
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(LibraryLoader.class.getSimpleName());
|
|
||||||
|
|
||||||
private static final String[] PATHS = {"/usr/lib/", "/usr/lib64/", "/usr/local/lib/", "/opt/local/lib/"};
|
private static final String[] PATHS = {"/usr/lib/", "/usr/lib64/", "/usr/local/lib/", "/opt/local/lib/"};
|
||||||
private static final String LIBRARY_NAME = "libunix_dbus_java";
|
private static final String LIBRARY_NAME = "libunix_dbus_java";
|
||||||
private static final String VERSION = "0.0.8";
|
private static final String VERSION = "0.0.8";
|
||||||
private static boolean loadSucceeded;
|
private static boolean loadSucceeded;
|
||||||
|
|
||||||
public static void load() {
|
public static LibraryLoader load() {
|
||||||
for (String path : PATHS) {
|
for (String path : PATHS) {
|
||||||
try {
|
try {
|
||||||
System.load(String.format("%s/%s.so.%s", path, LIBRARY_NAME, VERSION));
|
System.load(String.format("%s/%s.so.%s", path, LIBRARY_NAME, VERSION));
|
||||||
|
@ -40,10 +35,12 @@ public class LibraryLoader {
|
||||||
} catch (UnsatisfiedLinkError e) {
|
} catch (UnsatisfiedLinkError e) {
|
||||||
loadSucceeded = false;
|
loadSucceeded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!loadSucceeded) LOGGER.log(Level.WARNING, "libunix_dbus_java not found\n" +
|
return new LibraryLoader();
|
||||||
"Please, make sure you have the package libunix-dbus-java installed.");
|
}
|
||||||
|
|
||||||
|
public boolean succeed() {
|
||||||
|
return loadSucceeded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue