This commit is contained in:
Bill Burke 2016-09-22 19:57:39 -04:00
commit 8e65356891
288 changed files with 7282 additions and 1082 deletions

View file

@ -52,7 +52,7 @@ To stop the server press `Ctrl + C`.
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
* [JIRA](https://issues.jboss.org/projects/KEYCLOAK) - Issue tracker for bugs and feature requests

View file

@ -17,26 +17,21 @@
package org.keycloak.adapters;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.jboss.logging.Logger;
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.common.enums.RelativeUrlsUsed;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.enums.TokenStore;
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 java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.security.PublicKey;
import java.util.Map;
/**
@ -57,7 +52,7 @@ public class AdapterDeploymentContext {
* during the application deployment's life cycle.
*
* @param deployment A KeycloakConfigResolver, possibly missing the Auth
* Server URL and/or Realm Public Key
* Server URL
*/
public AdapterDeploymentContext(KeycloakDeployment deployment) {
this.deployment = deployment;
@ -79,7 +74,6 @@ public class AdapterDeploymentContext {
/**
* For single-tenant deployments, it complements KeycloakDeployment
* 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
* to the KeycloakConfigResolver .
@ -98,8 +92,8 @@ public class AdapterDeploymentContext {
if (deployment.getAuthServerBaseUrl() == null) return deployment;
KeycloakDeployment resolvedDeployment = resolveUrls(deployment, facade);
if (resolvedDeployment.getRealmKey() == null) {
resolveRealmKey(resolvedDeployment);
if (resolvedDeployment.getPublicKeyLocator() == null) {
throw new RuntimeException("KeycloakDeployment was never initialized through appropriate SPIs");
}
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.
* 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();
}
@Override
public String getJwksUrl() {
return (this.jwksUrl != null) ? this.jwksUrl : delegate.getJwksUrl();
}
@Override
public String getResourceName() {
return delegate.getResourceName();
@ -223,13 +183,13 @@ public class AdapterDeploymentContext {
}
@Override
public PublicKey getRealmKey() {
return delegate.getRealmKey();
public void setPublicKeyLocator(PublicKeyLocator publicKeyLocator) {
delegate.setPublicKeyLocator(publicKeyLocator);
}
@Override
public void setRealmKey(PublicKey realmKey) {
delegate.setRealmKey(realmKey);
public PublicKeyLocator getPublicKeyLocator() {
return delegate.getPublicKeyLocator();
}
@Override
@ -466,6 +426,26 @@ public class AdapterDeploymentContext {
public void setTokenMinimumTimeToLive(final int 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) {

View file

@ -19,6 +19,7 @@ package org.keycloak.adapters;
import org.jboss.logging.Logger;
import org.keycloak.RSATokenVerifier;
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade;
@ -84,7 +85,7 @@ public class BearerTokenRequestAuthenticator {
protected AuthOutcome authenticateToken(HttpFacade exchange, String tokenString) {
try {
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
} catch (VerificationException e) {
log.error("Failed to verify token", e);
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "invalid_token", e.getMessage());

View file

@ -22,6 +22,7 @@ import java.io.IOException;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.RSATokenVerifier;
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.common.VerificationException;
import org.keycloak.constants.AdapterConstants;
@ -73,7 +74,7 @@ public class CookieTokenStore {
try {
// 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;
if (idTokenString != null && idTokenString.length() > 0) {
try {

View file

@ -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) {
}
}
}
}

View file

@ -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);
}
}

View file

@ -21,6 +21,7 @@ import org.apache.http.client.HttpClient;
import org.jboss.logging.Logger;
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
import org.keycloak.adapters.authorization.PolicyEnforcer;
import org.keycloak.adapters.rotation.PublicKeyLocator;
import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.common.enums.RelativeUrlsUsed;
import org.keycloak.common.enums.SslRequired;
@ -29,7 +30,6 @@ import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.common.util.KeycloakUriBuilder;
import java.net.URI;
import java.security.PublicKey;
import java.util.HashMap;
import java.util.Map;
@ -43,7 +43,7 @@ public class KeycloakDeployment {
protected RelativeUrlsUsed relativeUrls;
protected String realm;
protected volatile PublicKey realmKey;
protected PublicKeyLocator publicKeyLocator;
protected String authServerBaseUrl;
protected String realmInfoUrl;
protected KeycloakUriBuilder authUrl;
@ -52,6 +52,7 @@ public class KeycloakDeployment {
protected String accountUrl;
protected String registerNodeUrl;
protected String unregisterNodeUrl;
protected String jwksUrl;
protected String principalAttribute = "sub";
protected String resourceName;
@ -79,13 +80,14 @@ public class KeycloakDeployment {
protected volatile int notBefore;
protected int tokenMinimumTimeToLive;
protected int minTimeBetweenJwksRequests;
private PolicyEnforcer policyEnforcer;
public KeycloakDeployment() {
}
public boolean isConfigured() {
return getRealm() != null && getRealmKey() != null && (isBearerOnly() || getAuthServerBaseUrl() != null);
return getRealm() != null && getPublicKeyLocator() != null && (isBearerOnly() || getAuthServerBaseUrl() != null);
}
public String getResourceName() {
@ -100,12 +102,12 @@ public class KeycloakDeployment {
this.realm = realm;
}
public PublicKey getRealmKey() {
return realmKey;
public PublicKeyLocator getPublicKeyLocator() {
return publicKeyLocator;
}
public void setRealmKey(PublicKey realmKey) {
this.realmKey = realmKey;
public void setPublicKeyLocator(PublicKeyLocator publicKeyLocator) {
this.publicKeyLocator = publicKeyLocator;
}
public String getAuthServerBaseUrl() {
@ -147,6 +149,7 @@ public class KeycloakDeployment {
accountUrl = authUrlBuilder.clone().path(ServiceUrlConstants.ACCOUNT_SERVICE_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();
jwksUrl = authUrlBuilder.clone().path(ServiceUrlConstants.JWKS_URL).build(getRealm()).toString();
}
public RelativeUrlsUsed getRelativeUrls() {
@ -181,6 +184,10 @@ public class KeycloakDeployment {
return unregisterNodeUrl;
}
public String getJwksUrl() {
return jwksUrl;
}
public void setResourceName(String resourceName) {
this.resourceName = resourceName;
}
@ -369,6 +376,14 @@ public class KeycloakDeployment {
this.tokenMinimumTimeToLive = tokenMinimumTimeToLive;
}
public int getMinTimeBetweenJwksRequests() {
return minTimeBetweenJwksRequests;
}
public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) {
this.minTimeBetweenJwksRequests = minTimeBetweenJwksRequests;
}
public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) {
this.policyEnforcer = policyEnforcer;
}

View file

@ -22,6 +22,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.jboss.logging.Logger;
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
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.util.PemUtils;
import org.keycloak.enums.TokenStore;
@ -59,11 +61,16 @@ public class KeycloakDeploymentBuilder {
PublicKey realmKey;
try {
realmKey = PemUtils.decodePublicKey(realmKeyPem);
HardcodedPublicKeyLocator pkLocator = new HardcodedPublicKeyLocator(realmKey);
deployment.setPublicKeyLocator(pkLocator);
} catch (Exception e) {
throw new RuntimeException(e);
}
deployment.setRealmKey(realmKey);
} else {
JWKPublicKeyLocator pkLocator = new JWKPublicKeyLocator();
deployment.setPublicKeyLocator(pkLocator);
}
if (adapterConfig.getSslRequired() != null) {
deployment.setSslRequired(SslRequired.valueOf(adapterConfig.getSslRequired().toUpperCase()));
} else {
@ -97,6 +104,7 @@ public class KeycloakDeploymentBuilder {
deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup());
deployment.setRegisterNodePeriod(adapterConfig.getRegisterNodePeriod());
deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive());
deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests());
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");

View file

@ -20,6 +20,7 @@ package org.keycloak.adapters;
import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants;
import org.keycloak.RSATokenVerifier;
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
import org.keycloak.adapters.spi.AdapterSessionStore;
import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome;
@ -342,7 +343,7 @@ public class OAuthRequestAuthenticator {
refreshToken = tokenResponse.getRefreshToken();
idTokenString = tokenResponse.getIdToken();
try {
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
if (idTokenString != null) {
try {
JWSInput input = new JWSInput(idTokenString);

View file

@ -17,7 +17,10 @@
package org.keycloak.adapters;
import java.security.PublicKey;
import org.jboss.logging.Logger;
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.spi.UserSessionManagement;
import org.keycloak.jose.jws.JWSInputException;
@ -198,7 +201,8 @@ public class PreAuthActionsHandler {
try {
JWSInput input = new JWSInput(token);
if (RSAProvider.verify(input, deployment.getRealmKey())) {
PublicKey publicKey = AdapterRSATokenVerifier.getPublicKey(input, deployment);
if (RSAProvider.verify(input, publicKey)) {
return input;
}
} catch (JWSInputException ignore) {

View file

@ -21,6 +21,7 @@ import org.jboss.logging.Logger;
import org.keycloak.AuthorizationContext;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.RSATokenVerifier;
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
import org.keycloak.common.VerificationException;
import org.keycloak.common.util.Time;
import org.keycloak.representations.AccessToken;
@ -130,7 +131,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
String tokenString = response.getToken();
AccessToken token = null;
try {
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
log.debug("Token Verification succeeded!");
} catch (VerificationException e) {
log.error("failed verification of token");

View file

@ -21,6 +21,7 @@ import org.jboss.logging.Logger;
import org.keycloak.RSATokenVerifier;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.authorization.client.AuthorizationDeniedException;
import org.keycloak.authorization.client.AuthzClient;
@ -120,7 +121,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
AuthorizationResponse authzResponse = authzClient.authorization(accessToken).authorize(authzRequest);
if (authzResponse != null) {
return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl());
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
}
return null;
@ -130,7 +131,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
if (token.getAuthorization() == null) {
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 {
EntitlementRequest request = new EntitlementRequest();
PermissionRequest permissionRequest = new PermissionRequest();
@ -139,7 +140,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
permissionRequest.setScopes(new HashSet<>(pathConfig.getScopes()));
request.addPermission(permissionRequest);
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) {

View file

@ -43,6 +43,7 @@ import org.keycloak.adapters.AdapterUtils;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
import org.keycloak.common.VerificationException;
import org.keycloak.common.util.FindFile;
import org.keycloak.representations.AccessToken;
@ -88,9 +89,6 @@ public abstract class AbstractKeycloakLoginModule implements LoginModule {
try {
InputStream is = FindFile.findFile(keycloakConfigFile);
KeycloakDeployment kd = KeycloakDeploymentBuilder.build(is);
if (kd.getRealmKey() == null) {
new AdapterDeploymentContext().resolveRealmKey(kd);
}
return kd;
} catch (RuntimeException 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 {
AccessToken token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
AccessToken token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
boolean verifyCaller;
if (deployment.isUseResourceRoleMappings()) {

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}

View file

@ -21,6 +21,8 @@ import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.junit.Test;
import org.keycloak.adapters.authentication.ClientIdAndSecretCredentialsProvider;
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.SslRequired;
import org.keycloak.enums.TokenStore;
@ -39,7 +41,11 @@ public class KeycloakDeploymentBuilderTest {
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak.json"));
assertEquals("demo", deployment.getRealm());
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(SslRequired.EXTERNAL, deployment.getSslRequired());
assertTrue(deployment.isUseResourceRoleMappings());
@ -62,12 +68,16 @@ public class KeycloakDeploymentBuilderTest {
assertEquals(TokenStore.COOKIE, deployment.getTokenStore());
assertEquals("email", deployment.getPrincipalAttribute());
assertEquals(10, deployment.getTokenMinimumTimeToLive());
assertEquals(20, deployment.getMinTimeBetweenJwksRequests());
}
@Test
public void loadNoClientCredentials() throws Exception {
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-no-credentials.json"));
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
assertTrue(deployment.getPublicKeyLocator() instanceof JWKPublicKeyLocator);
assertEquals(10, deployment.getMinTimeBetweenJwksRequests());
}
@Test

View file

@ -1,7 +1,6 @@
{
"realm": "demo",
"resource": "customer-portal",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "https://localhost:8443/auth",
"public-client": true,
"expose-token": true

View file

@ -29,5 +29,6 @@
"register-node-period": 1000,
"token-store": "cookie",
"principal-attribute": "email",
"token-minimum-time-to-live": 10
"token-minimum-time-to-live": 10,
"min-time-between-jwks-requests": 20
}

View file

@ -20,6 +20,7 @@ package org.keycloak.adapters.installed;
import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException;
import org.keycloak.RSATokenVerifier;
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
import org.keycloak.common.VerificationException;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
@ -213,7 +214,7 @@ public class KeycloakInstalled {
refreshToken = tokenResponse.getRefreshToken();
idTokenString = tokenResponse.getIdToken();
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
if (idTokenString != null) {
try {
JWSInput input = new JWSInput(idTokenString);

View file

@ -155,7 +155,6 @@
} else if (initOptions) {
if (initOptions.token || initOptions.refreshToken) {
setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken);
kc.timeSkew = initOptions.timeSkew || 0;
if (loginIframe.enable) {
setupCheckLoginIframe().success(function() {
@ -166,6 +165,8 @@
kc.onAuthError && kc.onAuthError();
if (initOptions.onLoad) {
onLoad();
} else {
initPromise.setError();
}
});
});
@ -177,6 +178,8 @@
kc.onAuthError && kc.onAuthError();
if (initOptions.onLoad) {
onLoad();
} else {
initPromise.setError();
}
});
}
@ -359,11 +362,10 @@
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) {
expiresIn -= minValidity;
}
return expiresIn < 0;
}
@ -378,7 +380,20 @@
minValidity = minValidity || 5;
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);
} else {
var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken;
@ -403,18 +418,21 @@
req.onreadystatechange = function () {
if (req.readyState == 4) {
if (req.status == 200) {
console.info('[KEYCLOAK] Token refreshed');
timeLocal = (timeLocal + new Date().getTime()) / 2;
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();
for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
p.setSuccess(true);
}
} else {
console.warn('[KEYCLOAK] Failed to refresh token');
kc.onAuthRefreshError && kc.onAuthRefreshError();
for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
p.setError(true);
@ -525,18 +543,16 @@
function authSuccess(accessToken, refreshToken, idToken, fulfillPromise) {
timeLocal = (timeLocal + new Date().getTime()) / 2;
setToken(accessToken, refreshToken, idToken);
setToken(accessToken, refreshToken, idToken, timeLocal);
if ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) ||
(kc.refreshTokenParsed && kc.refreshTokenParsed.nonce != oauth.storedNonce) ||
(kc.idTokenParsed && kc.idTokenParsed.nonce != oauth.storedNonce)) {
console.log('invalid nonce!');
console.info('[KEYCLOAK] Invalid nonce, clearing token');
kc.clearToken();
promise && promise.setError();
} else {
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
if (fulfillPromise) {
kc.onAuthSuccess && kc.onAuthSuccess();
promise && promise.setSuccess();
@ -609,7 +625,7 @@
return promise.promise;
}
function setToken(token, refreshToken, idToken) {
function setToken(token, refreshToken, idToken, timeLocal) {
if (kc.tokenTimeoutHandle) {
clearTimeout(kc.tokenTimeoutHandle);
kc.tokenTimeoutHandle = null;
@ -628,7 +644,17 @@
kc.realmAccess = kc.tokenParsed.realm_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.timeSkew == -1) {
kc.onTokenExpired();
} else {
var expiresIn = (kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew) * 1000;
if (expiresIn <= 0) {
kc.onTokenExpired();
@ -636,6 +662,7 @@
kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn);
}
}
}
} else {
delete kc.token;

View file

@ -69,6 +69,5 @@ public class HttpSessionManager implements HttpSessionListener, UserSessionManag
session.invalidate();
}
}
sessions.clear();
}
}

View 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();
}
}

View file

@ -32,6 +32,7 @@ public class Version {
public static String VERSION;
public static String RESOURCES_VERSION;
public static String BUILD_TIME;
public static String DEFAULT_PROFILE;
static {
Properties props = new Properties();
@ -40,6 +41,7 @@ public class Version {
props.load(is);
Version.NAME = props.getProperty("name");
Version.NAME_HTML = props.getProperty("name-html");
Version.DEFAULT_PROFILE = props.getProperty("default-profile");
Version.VERSION = props.getProperty("version");
Version.BUILD_TIME = props.getProperty("build-time");
Version.RESOURCES_VERSION = Version.VERSION.toLowerCase();

View file

@ -19,3 +19,4 @@ name=${product.name}
name-html=${product.name-html}
version=${product.version}
build-time=${product.build-time}
default-profile=${product.default-profile}

View file

@ -38,6 +38,12 @@ public class RSATokenVerifier {
public static AccessToken verifyToken(String tokenString, PublicKey realmKey, String realmUrl, boolean checkActive, boolean checkTokenType) throws VerificationException {
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();
if (user == null) {
throw new VerificationException("Token user was null.");
@ -60,9 +66,9 @@ public class RSATokenVerifier {
throw new VerificationException("Token is not active.");
}
return token;
}
public static AccessToken toAccessToken(String tokenString, PublicKey realmKey) throws VerificationException {
JWSInput input;
try {
@ -81,6 +87,23 @@ public class RSATokenVerifier {
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 {
try {
return RSAProvider.verify(input, realmKey);

View file

@ -30,5 +30,6 @@ public interface ServiceUrlConstants {
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_UNREGISTER_NODE_PATH = "/realms/{realm-name}/clients-managements/unregister-node";
public static final String JWKS_URL = "/realms/{realm-name}/protocol/openid-connect/certs";
}

View file

@ -21,7 +21,6 @@ import com.fasterxml.jackson.core.type.TypeReference;
import org.keycloak.common.util.Base64Url;
import org.keycloak.util.JsonSerialization;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
@ -66,8 +65,8 @@ public class JWKParser {
}
public PublicKey toPublicKey() {
String algorithm = jwk.getKeyType();
if (isAlgorithmSupported(algorithm)) {
String keyType = jwk.getKeyType();
if (isKeyTypeSupported(keyType)) {
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()));
@ -77,12 +76,12 @@ public class JWKParser {
throw new RuntimeException(e);
}
} else {
throw new RuntimeException("Unsupported algorithm " + algorithm);
throw new RuntimeException("Unsupported keyType " + keyType);
}
}
public boolean isAlgorithmSupported(String algorithm) {
return RSAPublicJWK.RSA.equals(algorithm);
public boolean isKeyTypeSupported(String keyType) {
return RSAPublicJWK.RSA.equals(keyType);
}
}

View file

@ -36,7 +36,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
"client-keystore", "client-keystore-password", "client-key-password",
"always-refresh-token",
"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"
})
public class AdapterConfig extends BaseAdapterConfig {
@ -71,6 +71,8 @@ public class AdapterConfig extends BaseAdapterConfig {
protected Boolean turnOffChangeSessionIdOnLogin;
@JsonProperty("token-minimum-time-to-live")
protected int tokenMinimumTimeToLive = 0;
@JsonProperty("min-time-between-jwks-requests")
protected int minTimeBetweenJwksRequests = 10;
@JsonProperty("policy-enforcer")
protected PolicyEnforcerConfig policyEnforcerConfig;
@ -216,4 +218,11 @@ public class AdapterConfig extends BaseAdapterConfig {
this.tokenMinimumTimeToLive = tokenMinimumTimeToLive;
}
public int getMinTimeBetweenJwksRequests() {
return minTimeBetweenJwksRequests;
}
public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) {
this.minTimeBetweenJwksRequests = minTimeBetweenJwksRequests;
}
}

View file

@ -123,10 +123,15 @@ public class IdentityProviderRepresentation {
this.updateProfileFirstLoginMode = updateProfileFirstLoginMode;
}
/**
* @deprecated Replaced by configuration option in identity provider authenticator
*/
@Deprecated
public boolean isAuthenticateByDefault() {
return authenticateByDefault;
}
@Deprecated
public void setAuthenticateByDefault(boolean authenticateByDefault) {
this.authenticateByDefault = authenticateByDefault;
}

View file

@ -123,6 +123,8 @@ public class RealmRepresentation {
protected String resetCredentialsFlow;
protected String clientAuthenticationFlow;
protected Map<String, String> attributes;
protected String keycloakVersion;
@Deprecated
@ -864,4 +866,12 @@ public class RealmRepresentation {
return identityProviders != null && !identityProviders.isEmpty();
}
public void setAttributes(Map<String, String> attributes) {
this.attributes = attributes;
}
public Map<String, String> getAttributes() {
return attributes;
}
}

View file

@ -123,10 +123,12 @@ public class UserRepresentation {
this.enabled = enabled;
}
@Deprecated
public Boolean isTotp() {
return totp;
}
@Deprecated
public void setTotp(Boolean totp) {
this.totp = totp;
}

View file

@ -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;
}
}

View file

@ -32,6 +32,7 @@ public class ServerInfoRepresentation {
private SystemInfoRepresentation systemInfo;
private MemoryInfoRepresentation memoryInfo;
private ProfileInfoRepresentation profileInfo;
private Map<String, List<ThemeInfoRepresentation>> themes;
@ -66,6 +67,14 @@ public class ServerInfoRepresentation {
this.memoryInfo = memoryInfo;
}
public ProfileInfoRepresentation getProfileInfo() {
return profileInfo;
}
public void setProfileInfo(ProfileInfoRepresentation profileInfo) {
this.profileInfo = profileInfo;
}
public Map<String, List<ThemeInfoRepresentation>> getThemes() {
return themes;
}

View 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;
}
}

View file

@ -17,17 +17,6 @@
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.Test;
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.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>
*/
@ -138,6 +134,18 @@ public class JsonParserTest {
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
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}";

View file

@ -112,12 +112,6 @@
<type>zip</type>
<destFileName>keycloak-examples-${project.version}.zip</destFileName>
</artifactItem>
<artifactItem>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-src-dist</artifactId>
<type>zip</type>
<destFileName>keycloak-src-${project.version}.zip</destFileName>
</artifactItem>
</artifactItems>
<outputDirectory>target/${project.version}</outputDirectory>
</configuration>

View file

@ -40,7 +40,6 @@
<module>proxy-dist</module>
<module>server-dist</module>
<module>server-overlay</module>
<module>src-dist</module>
<module>feature-packs</module>
</modules>

View file

@ -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>

View file

@ -1,6 +1,5 @@
{
"realm": "hello-world-authz",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "http://localhost:8080/auth",
"ssl-required": "external",
"resource": "hello-world-authz-service",

View file

@ -43,7 +43,7 @@ public class AuthorizationClientExample {
}
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();
// query the server for a resource with a given name
@ -51,8 +51,9 @@ public class AuthorizationClientExample {
.resource()
.findByFilter("name=Default Resource");
// obtian a 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
// 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
String eat = getEntitlementAPIToken(authzClient);
// create an entitlement request
@ -63,7 +64,8 @@ public class AuthorizationClientExample {
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);
String rpt = response.getRpt();
@ -79,7 +81,7 @@ public class AuthorizationClientExample {
}
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();
// 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
AuthzClient authzClient = AuthzClient.create();
// obtian a 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
// 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
String eat = getEntitlementAPIToken(authzClient);
// create an entitlement request
@ -123,7 +126,8 @@ public class AuthorizationClientExample {
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);
String rpt = response.getRpt();
@ -133,7 +137,7 @@ public class AuthorizationClientExample {
}
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();
// obtian a Entitlement API Token in order to get access to the Entitlement API.

View file

@ -1,6 +1,5 @@
{
"realm": "photoz",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "http://localhost:8080/auth",
"ssl-required" : "external",
"resource" : "photoz-html5-client",

View file

@ -1,6 +1,5 @@
{
"realm": "photoz",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "http://localhost:8080/auth",
"ssl-required": "external",
"resource": "photoz-restful-api",

View file

@ -1,6 +1,5 @@
{
"realm": "servlet-authz",
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "http://localhost:8080/auth",
"ssl-required" : "external",
"resource" : "servlet-authz-app",

View file

@ -1,7 +1,6 @@
{
"realm" : "basic-auth",
"resource" : "basic-auth-service",
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "/auth",
"ssl-required" : "external",
"enable-basic-auth" : "true",

View file

@ -1,7 +1,6 @@
{
"realm" : "facebook-identity-provider-realm",
"resource" : "facebook-authentication",
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "/auth",
"ssl-required" : "external",
"public-client" : true

View file

@ -1,7 +1,6 @@
{
"realm" : "google-identity-provider-realm",
"resource" : "google-authentication",
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "/auth",
"ssl-required" : "external",
"public-client" : true

View file

@ -1,7 +1,6 @@
{
"realm" : "twitter-identity-provider-realm",
"resource" : "twitter-authentication",
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "/auth",
"ssl-required" : "external",
"public-client" : true

View file

@ -1,6 +1,5 @@
{
"realm" : "cors",
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "http://localhost-auth:8080/auth",
"ssl-required" : "external",
"resource" : "angular-cors-product",

View file

@ -1,7 +1,6 @@
{
"realm" : "cors",
"resource" : "cors-database-service",
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "http://localhost-auth:8080/auth",
"bearer-only" : true,
"ssl-required": "external",

View file

@ -1,6 +1,5 @@
{
"realm" : "demo",
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "/auth",
"ssl-required" : "external",
"resource" : "angular-product",

View file

@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-demo-parent</artifactId>
<groupId>org.keycloak</groupId>
<version></version>
<version>2.2.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View file

@ -0,0 +1,4 @@
app/*.js
app/*.js.map
node_modules
typings

View file

@ -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');
}
}

View file

@ -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 {}

View file

@ -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');
}
}

View file

@ -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');
});
}
});
}
}

View file

@ -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');
});
}
});
}
}

View file

@ -1,14 +1,11 @@
import 'rxjs/Rx';
import {bootstrap} from 'angular2/platform/browser';
import {HTTP_BINDINGS} from 'angular2/http';
import {KeycloakService} from './keycloak';
import {AppComponent} from './app';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
KeycloakService.init().then(
o=>{
bootstrap(AppComponent,[HTTP_BINDINGS, KeycloakService]);
},
x=>{
window.location.reload();
}
);
import {KeycloakService} from './keycloak.service';
KeycloakService.init()
.then(() => {
const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);
})
.catch(() => window.location.reload());

View file

@ -2,48 +2,23 @@
<html>
<head>
<title>Angular 2 QuickStart</title>
<meta charset="UTF-8">
<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>
<!-- 3. Display the application -->
<body>
<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>
</html>

View file

@ -1,6 +1,5 @@
{
"realm": "demo",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "/auth",
"ssl-required": "external",
"resource": "angular2-product",

View file

@ -2,24 +2,36 @@
"name": "angular2-product-app",
"version": "1.0.0",
"scripts": {
"start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite\" ",
"lite": "lite-server",
"postinstall": "typings install",
"tsc": "tsc",
"tsc:w": "tsc -w",
"lite": "lite-server",
"start": "concurrent \"npm run tsc:w\" \"npm run lite\" "
"typings": "typings"
},
"license": "ISC",
"dependencies": {
"angular2": "2.0.0-beta.3",
"systemjs": "0.19.6",
"es6-promise": "^3.0.2",
"es6-shim": "^0.33.3",
"reflect-metadata": "0.1.2",
"rxjs": "5.0.0-beta.0",
"zone.js": "0.5.11"
"@angular/common": "2.0.0",
"@angular/compiler": "2.0.0",
"@angular/core": "2.0.0",
"@angular/forms": "2.0.0",
"@angular/http": "2.0.0",
"@angular/platform-browser": "2.0.0",
"@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": {
"concurrently": "^1.0.0",
"lite-server": "^2.0.1",
"typescript": "^1.7.5"
"concurrently": "^2.2.0",
"lite-server": "^2.2.2",
"typescript": "^2.0.2",
"typings": "^1.3.2"
}
}

View file

@ -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);

View file

@ -1,15 +1,12 @@
{
"compilerOptions": {
"target": "es5",
"module": "system",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": false,
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": false
},
"exclude": [
"node_modules"
]
}
}

View file

@ -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"
}
}

View file

@ -1,6 +1,5 @@
{
"realm" : "demo",
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "http://localhost:8080/auth",
"ssl-required" : "external",
"resource" : "customer-portal-cli",

View file

@ -1,7 +1,6 @@
{
"realm": "demo",
"resource": "customer-portal-filter",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "/auth",
"ssl-required" : "external",
"expose-token": true,

View file

@ -1,6 +1,5 @@
{
"realm" : "demo",
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "/auth",
"ssl-required" : "external",
"resource" : "customer-portal-js",

View file

@ -1,7 +1,6 @@
{
"realm": "demo",
"resource": "customer-portal",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "/auth",
"ssl-required" : "external",
"expose-token": true,

View file

@ -1,7 +1,6 @@
{
"realm" : "demo",
"resource" : "database-service",
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "/auth",
"bearer-only" : true,
"ssl-required" : "external"

View file

@ -1,7 +1,6 @@
{
"realm": "demo",
"resource": "offline-access-portal",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "/auth",
"ssl-required" : "external",
"credentials": {

View file

@ -51,6 +51,7 @@
<module>example-ear</module>
<module>admin-access-app</module>
<module>angular-product-app</module>
<module>angular2-product-app</module>
<module>database-service</module>
<module>third-party</module>
<module>third-party-cdi</module>

View file

@ -1,7 +1,6 @@
{
"realm" : "demo",
"resource" : "product-portal",
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "/auth",
"ssl-required" : "external",
"credentials" : {

View file

@ -41,6 +41,7 @@ import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.keycloak.OAuth2Constants;
import org.keycloak.RSATokenVerifier;
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
import org.keycloak.common.VerificationException;
import org.keycloak.adapters.KeycloakDeployment;
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 {
String token = tokenResponse.getToken();
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(REFRESH_TOKEN, refreshToken);
req.getSession().setAttribute(TOKEN_PARSED, tokenParsed);

View file

@ -1,6 +1,5 @@
{
"realm" : "demo",
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "http://localhost:8080/auth",
"ssl-required" : "external",
"resource" : "product-sa-client",

View file

@ -1,6 +1,5 @@
{
"realm" : "demo",
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "http://localhost:8080/auth",
"ssl-required" : "external",
"resource" : "product-sa-client-jwt-auth",

View file

@ -26,7 +26,6 @@
<bean id="kcAdapterConfig" class="org.keycloak.representations.adapters.config.AdapterConfig">
<property name="realm" value="demo"/>
<property name="resource" value="admin-camel-endpoint"/>
<property name="realmKey" value="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"/>
<property name="bearerOnly" value="true"/>
<property name="authServerUrl" value="http://localhost:8080/auth" />
<property name="sslRequired" value="EXTERNAL"/>

View file

@ -1,7 +1,6 @@
{
"realm": "demo",
"resource": "customer-portal",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "http://localhost:8080/auth",
"ssl-required" : "external",
"credentials": {

View file

@ -1,7 +1,6 @@
{
"realm": "demo",
"resource": "builtin-cxf-app",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "http://localhost:8080/auth",
"ssl-required" : "external",
"credentials": {

View file

@ -32,7 +32,6 @@
<bean id="kcAdapterConfig" class="org.keycloak.representations.adapters.config.AdapterConfig">
<property name="realm" value="demo"/>
<property name="resource" value="custom-cxf-endpoint"/>
<property name="realmKey" value="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"/>
<property name="bearerOnly" value="true"/>
<property name="authServerUrl" value="http://localhost:8080/auth" />
<property name="sslRequired" value="EXTERNAL"/>

View file

@ -1,7 +1,6 @@
{
"realm": "demo",
"resource": "external-config",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "http://localhost:8080/auth",
"ssl-required" : "external",
"credentials": {

View file

@ -1,7 +1,6 @@
{
"realm": "demo",
"resource": "ssh-jmx-admin-client",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"ssl-required" : "external",
"auth-server-url" : "http://localhost:8080/auth",
"credentials": {

View file

@ -1,7 +1,6 @@
{
"realm": "demo",
"resource": "product-portal",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "http://localhost:8080/auth",
"ssl-required" : "external",
"credentials": {

View file

@ -1,6 +1,5 @@
{
"realm" : "example",
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "/auth",
"ssl-required" : "external",
"resource" : "js-console",

View file

@ -1,7 +1,6 @@
{
"realm" : "kerberos-demo",
"resource" : "kerberos-app",
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "/auth",
"ssl-required" : "external",
"credentials": {

View file

@ -1,7 +1,6 @@
{
"realm" : "ldap-demo",
"resource" : "ldap-app",
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "/auth",
"ssl-required" : "external",
"credentials": {

View file

@ -1,7 +1,6 @@
{
"realm" : "tenant1",
"resource" : "multi-tenant",
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "http://localhost:8080/auth",
"ssl-required" : "external",
"credentials" : {

View file

@ -1,7 +1,6 @@
{
"realm" : "tenant2",
"resource" : "multi-tenant",
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDA0oJjgPQJhnVhOo51KauQGfLLreMFu64OJdKXRnfvAQJQTuKNwc5JrR63l/byyW1B6FgclABF818TtLvMCAkn4EuFwQZCZhg3x3+lFGiB/IzC6UAt4Bi0JQrTbdh83/U97GIPegvaDqiqEiQESEkbCZWxM6sh/34hQaAhCaFpMwIDAQAB",
"auth-server-url" : "http://localhost:8080/auth",
"ssl-required" : "external",
"credentials" : {

View file

@ -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"
Then registering the provider by editing keycloak-server.json and adding the module to the providers field:
"providers": [
....
"module:org.keycloak.examples.secret-question"
],
Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element:
<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.

View file

@ -70,7 +70,7 @@ public class SecretQuestionCredentialProvider implements CredentialProvider, Cre
CredentialModel secret = new CredentialModel();
secret.setType(SECRET_QUESTION);
secret.setValue(credInput.getValue());
secret.setCreatedDate(Time.toMillis(Time.currentTime()));
secret.setCreatedDate(Time.currentTimeMillis());
session.userCredentialManager().createCredential(realm ,user, secret);
} else {
creds.get(0).setValue(credInput.getValue());

View file

@ -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"
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.domain-extension-example"
],
<providers>
...
<provider>module:org.keycloak.examples.domain-extension-example</provider>
</providers>
Then start (or restart) the server.

View file

@ -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"
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.event-sysout"
],
<providers>
...
<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,
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.
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": {
"sysout": {
"exclude": [ "REFRESH_TOKEN", "CODE_TO_TOKEN" ]
}
}
<spi name="eventsListener">
<provider name="sysout">
<properties>
<property name="exclude-events" value="[&quot;REFRESH_TOKEN&quot;, &quot;CODE_TO_TOKEN&quot;]"/>
</properties>
</provider
</spi>

View file

@ -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"
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.event-inmem"
],
<providers>
...
<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": {
"provider": "jpa"
}
<spi name="eventsStore">
<default-provider>jpa</default-provider>
</spi>
to:
"eventsStore": {
"provider": "in-mem"
}
<spi name="eventsStore">
<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,
followed by config. Set the toggle for Enabled to ON. After this try to logout and login again then open the Events tab

View file

@ -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"
Then registering the provider by editing keycloak-server.json and adding the module to the providers field:
"providers": [
....
"module:org.keycloak.examples.userprops"
],
Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element:
<providers>
...
<provider>module:org.keycloak.examples.userprops</provider>
</providers>
You will then have to restart the authentication server.

View file

@ -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
*/

View file

@ -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"
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.hello-rest-example"
],
<providers>
...
<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_.
You can also invoke the endpoint for other realms by replacing `master` with the realm name in the above url.

View file

@ -17,14 +17,14 @@ Alternatively you can deploy as modules. This can be done by first running:
mvn clean install
$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:
"theme": {
"module": {
"modules": [ "org.keycloak.example.themes" ]
}
}
Then open `standalone/configuration/standalone.xml` and register the theme module by adding:
<theme>
...
<modules>
<module>org.keycloak.example.themes</module>
</modules>
</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 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`).

View file

@ -23,6 +23,20 @@
<target>${maven.compiler.target}</target>
</configuration>
</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>
</build>

View file

@ -16,22 +16,17 @@
*/
package cx.ath.matthew;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author <a href="mailto:bruno@abstractj.org">Bruno Oliveira</a>.
*/
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 LIBRARY_NAME = "libunix_dbus_java";
private static final String VERSION = "0.0.8";
private static boolean loadSucceeded;
public static void load() {
public static LibraryLoader load() {
for (String path : PATHS) {
try {
System.load(String.format("%s/%s.so.%s", path, LIBRARY_NAME, VERSION));
@ -40,10 +35,12 @@ public class LibraryLoader {
} catch (UnsatisfiedLinkError e) {
loadSucceeded = false;
}
}
if (!loadSucceeded) LOGGER.log(Level.WARNING, "libunix_dbus_java not found\n" +
"Please, make sure you have the package libunix-dbus-java installed.");
return new LibraryLoader();
}
public boolean succeed() {
return loadSucceeded;
}
}

Some files were not shown because too many files have changed in this diff Show more