Compare commits

..
Author SHA1 Message Date
keycloak-bot 9481a351ed Set version to 3.0.0.Final 2017-03-22 14:14:35 +00:00
Stian Thorgersen f98dcf2d7e Merge pull request #3967 from vramik/3.0.x
KEYCLOAK-4638 Migrator to 3.0.0 contains code coppied from 2.5.0
2017-03-22 14:09:57 +01:00
vramik 27807c9bff KEYCLOAK-4638 Migrator to 3.0.0 contains code coppied from 2.5.0 2017-03-22 13:08:50 +01:00
keycloak-bot 22b56679e3 Set version to 3.0.1.Final-SNAPSHOT 2017-03-22 08:35:36 +00:00
keycloak-bot 3ba8989681 Set version to 3.0.0.Final 2017-03-22 07:52:15 +00:00
keycloak-bot bdd20b9699 Set version to 3.0.0.Final-SNAPSHOT 2017-03-16 13:24:15 +00:00
keycloak-bot 1cdecab650 Set version to 3.0.0.CR1 2017-03-16 12:04:07 +00:00
Stian Thorgersen de1650ccc4 Added jboss parent to boms parent 2017-03-16 11:23:40 +01:00
2481 changed files with 37603 additions and 243243 deletions

No files matched your search

+1 -1
View File
@@ -25,7 +25,7 @@ before_script:
- export MAVEN_SKIP_RC=true
install:
- travis_wait 60 mvn install --no-snapshot-updates -Pdistribution -DskipTestsuite -B -V -q
- travis_wait 60 mvn install --no-snapshot-updates -Pdistribution -DskipTests=true -B -V -q
script:
- ./travis-run-tests.sh $TESTS
+3 -3
View File
@@ -41,10 +41,10 @@ To start Keycloak during development first build as specified above, then run:
mvn -f testsuite/integration/pom.xml exec:java -Pkeycloak-server
To start Keycloak from the server distribution first build the distribution it as specified above, then run:
To start Keycloak from the appliance distribution first build the distribution it as specified above, then run:
tar xfz distribution/server-dist/target/keycloak-<VERSION>.tar.gz
cd keycloak-<VERSION>
tar xfz distribution/appliance-dist/target/keycloak-appliance-dist-all-<VERSION>.tar.gz
cd keycloak-appliance-dist-all-<VERSION>/keycloak
bin/standalone.sh
To stop the server press `Ctrl + C`.
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -101,7 +101,6 @@ public class AuthenticatedActionsHandler {
if (!deployment.isCors()) return false;
KeycloakSecurityContext securityContext = facade.getSecurityContext();
String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN);
String exposeHeaders = deployment.getCorsExposedHeaders();
String requestOrigin = UriUtils.getOrigin(facade.getRequest().getURI());
log.debugv("Origin: {0} uri: {1}", origin, facade.getRequest().getURI());
if (securityContext != null && origin != null && !origin.equals(requestOrigin)) {
@@ -125,9 +124,6 @@ public class AuthenticatedActionsHandler {
facade.getResponse().setStatus(200);
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
if (exposeHeaders != null) {
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, exposeHeaders);
}
} else {
log.debugv("cors validation not needed as we're not a secure session or origin header was null: {0}", facade.getRequest().getURI());
}
@@ -30,5 +30,4 @@ public interface CorsHeaders {
String ORIGIN = "Origin";
String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
}
@@ -170,8 +170,8 @@ public class HttpClientBuilder {
return this;
}
public HttpClientBuilder disableCookieCache(boolean disable) {
this.disableCookieCache = disable;
public HttpClientBuilder disableCookieCache() {
this.disableCookieCache = true;
return this;
}
@@ -334,7 +334,7 @@ public class HttpClientBuilder {
}
public HttpClient build(AdapterHttpClientConfig adapterConfig) {
disableCookieCache(true); // disable cookie cache as we don't want sticky sessions for load balancing
disableCookieCache(); // disable cookie cache as we don't want sticky sessions for load balancing
String truststorePath = adapterConfig.getTruststore();
if (truststorePath != null) {
@@ -35,8 +35,6 @@ import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
* @version $Revision: 1 $
*/
public class KeycloakDeployment {
@@ -75,7 +73,6 @@ public class KeycloakDeployment {
protected int corsMaxAge = -1;
protected String corsAllowedHeaders;
protected String corsAllowedMethods;
protected String corsExposedHeaders;
protected boolean exposeToken;
protected boolean alwaysRefreshToken;
protected boolean registerNodeAtStartup;
@@ -88,12 +85,6 @@ public class KeycloakDeployment {
protected int publicKeyCacheTtl;
private PolicyEnforcer policyEnforcer;
// https://tools.ietf.org/html/rfc7636
protected boolean pkce = false;
protected boolean ignoreOAuthQueryParameter;
protected Map<String, String> redirectRewriteRules;
public KeycloakDeployment() {
}
@@ -331,14 +322,6 @@ public class KeycloakDeployment {
this.corsAllowedMethods = corsAllowedMethods;
}
public String getCorsExposedHeaders() {
return corsExposedHeaders;
}
public void setCorsExposedHeaders(String corsExposedHeaders) {
this.corsExposedHeaders = corsExposedHeaders;
}
public boolean isExposeToken() {
return exposeToken;
}
@@ -431,31 +414,4 @@ public class KeycloakDeployment {
public PolicyEnforcer getPolicyEnforcer() {
return policyEnforcer;
}
// https://tools.ietf.org/html/rfc7636
public boolean isPkce() {
return pkce;
}
public void setPkce(boolean pkce) {
this.pkce = pkce;
}
public void setIgnoreOAuthQueryParameter(boolean ignoreOAuthQueryParameter) {
this.ignoreOAuthQueryParameter = ignoreOAuthQueryParameter;
}
public boolean isOAuthQueryParameterEnabled() {
return !this.ignoreOAuthQueryParameter;
}
public Map<String, String> getRedirectRewriteRules() {
return redirectRewriteRules;
}
public void setRewriteRedirectRules(Map<String, String> redirectRewriteRules) {
this.redirectRewriteRules = redirectRewriteRules;
}
}
@@ -37,8 +37,6 @@ import java.security.PublicKey;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
* @version $Revision: 1 $
*/
public class KeycloakDeploymentBuilder {
@@ -98,12 +96,6 @@ public class KeycloakDeploymentBuilder {
deployment.setCorsMaxAge(adapterConfig.getCorsMaxAge());
deployment.setCorsAllowedHeaders(adapterConfig.getCorsAllowedHeaders());
deployment.setCorsAllowedMethods(adapterConfig.getCorsAllowedMethods());
deployment.setCorsExposedHeaders(adapterConfig.getCorsExposedHeaders());
}
// https://tools.ietf.org/html/rfc7636
if (adapterConfig.isPkce()) {
deployment.setPkce(true);
}
deployment.setBearerOnly(adapterConfig.isBearerOnly());
@@ -115,8 +107,6 @@ public class KeycloakDeploymentBuilder {
deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive());
deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests());
deployment.setPublicKeyCacheTtl(adapterConfig.getPublicKeyCacheTtl());
deployment.setIgnoreOAuthQueryParameter(adapterConfig.isIgnoreOAuthQueryParameter());
deployment.setRewriteRedirectRules(adapterConfig.getRedirectRewriteRules());
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");
@@ -25,6 +25,7 @@ import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.common.VerificationException;
import org.keycloak.common.util.Encode;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.UriUtils;
import org.keycloak.constants.AdapterConstants;
@@ -37,10 +38,7 @@ import org.keycloak.representations.IDToken;
import org.keycloak.util.TokenUtil;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import java.util.logging.Level;
import java.util.concurrent.atomic.AtomicLong;
/**
@@ -143,7 +141,6 @@ public class OAuthRequestAuthenticator {
protected String getRedirectUri(String state) {
String url = getRequestUrl();
log.debugf("callback uri: %s", url);
if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
int port = sslRedirectPort();
if (port < 0) {
@@ -173,7 +170,7 @@ public class OAuthRequestAuthenticator {
KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone()
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
.queryParam(OAuth2Constants.REDIRECT_URI, rewrittenRedirectUri(url))
.queryParam(OAuth2Constants.REDIRECT_URI, Encode.encodeQueryParamAsIs(url)) // Need to encode uri ourselves as queryParam() will not encode % characters.
.queryParam(OAuth2Constants.STATE, state)
.queryParam("login", "true");
if(loginHint != null && loginHint.length() > 0){
@@ -323,11 +320,10 @@ public class OAuthRequestAuthenticator {
AccessTokenResponse tokenResponse = null;
strippedOauthParametersRequestUri = stripOauthParametersFromRedirect();
try {
// For COOKIE store we don't have httpSessionId and single sign-out won't be available
String httpSessionId = deployment.getTokenStore() == TokenStore.SESSION ? reqAuthenticator.changeHttpSessionId(true) : null;
tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, rewrittenRedirectUri(strippedOauthParametersRequestUri), httpSessionId);
tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, strippedOauthParametersRequestUri, httpSessionId);
} catch (ServerRequest.HttpFailure failure) {
log.error("failed to turn code into token");
log.error("status from server: " + failure.getStatus());
@@ -379,23 +375,6 @@ public class OAuthRequestAuthenticator {
.replaceQueryParam(OAuth2Constants.STATE, null);
return builder.build().toString();
}
private String rewrittenRedirectUri(String originalUri) {
Map<String, String> rewriteRules = deployment.getRedirectRewriteRules();
if(rewriteRules != null && !rewriteRules.isEmpty()) {
try {
URL url = new URL(originalUri);
Map.Entry<String, String> rule = rewriteRules.entrySet().iterator().next();
StringBuilder redirectUriBuilder = new StringBuilder(url.getProtocol());
redirectUriBuilder.append("://"+ url.getAuthority());
redirectUriBuilder.append(url.getPath().replaceFirst(rule.getKey(), rule.getValue()));
return redirectUriBuilder.toString();
} catch (MalformedURLException ex) {
log.error("Not a valid request url");
throw new RuntimeException(ex);
}
}
return originalUri;
}
}
@@ -22,8 +22,6 @@ import org.keycloak.adapters.spi.HttpFacade;
/**
* @author <a href="mailto:froehlich.ch@gmail.com">Christian Froehlich</a>
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
* @version $Revision: 1 $
*/
public class QueryParamterTokenRequestAuthenticator extends BearerTokenRequestAuthenticator {
@@ -35,9 +33,6 @@ public class QueryParamterTokenRequestAuthenticator extends BearerTokenRequestAu
}
public AuthOutcome authenticate(HttpFacade exchange) {
if(!deployment.isOAuthQueryParameterEnabled()) {
return AuthOutcome.NOT_ATTEMPTED;
}
tokenString = null;
tokenString = getAccessTokenFromQueryParamter(exchange);
if (tokenString == null || tokenString.trim().isEmpty()) {
@@ -33,8 +33,6 @@ import org.keycloak.constants.AdapterConstants;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.util.JsonSerialization;
import org.jboss.logging.Logger;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -48,8 +46,6 @@ import java.util.List;
*/
public class ServerRequest {
private static Logger logger = Logger.getLogger(ServerRequest.class);
public static class HttpFailure extends Exception {
private int status;
private String error;
@@ -140,62 +136,6 @@ public class ServerRequest {
}
}
// https://tools.ietf.org/html/rfc7636#section-4
public static AccessTokenResponse invokeAccessCodeToToken(KeycloakDeployment deployment, String code, String redirectUri, String sessionId, String codeVerifier) throws IOException, HttpFailure {
List<NameValuePair> formparams = new ArrayList<>();
redirectUri = stripOauthParametersFromRedirect(redirectUri);
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, "authorization_code"));
formparams.add(new BasicNameValuePair(OAuth2Constants.CODE, code));
formparams.add(new BasicNameValuePair(OAuth2Constants.REDIRECT_URI, redirectUri));
if (sessionId != null) {
formparams.add(new BasicNameValuePair(AdapterConstants.CLIENT_SESSION_STATE, sessionId));
formparams.add(new BasicNameValuePair(AdapterConstants.CLIENT_SESSION_HOST, HostUtils.getHostName()));
}
// https://tools.ietf.org/html/rfc7636#section-4
if (codeVerifier != null) {
logger.debugf("add to POST parameters of Token Request, codeVerifier = %s", codeVerifier);
formparams.add(new BasicNameValuePair(OAuth2Constants.CODE_VERIFIER, codeVerifier));
} else {
logger.debug("add to POST parameters of Token Request without codeVerifier");
}
HttpPost post = new HttpPost(deployment.getTokenUrl());
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
post.setEntity(form);
HttpResponse response = deployment.getClient().execute(post);
int status = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
if (status != 200) {
error(status, entity);
}
if (entity == null) {
throw new HttpFailure(status, null);
}
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);
try {
return JsonSerialization.readValue(json, AccessTokenResponse.class);
} catch (IOException e) {
throw new IOException(json, e);
}
} finally {
try {
is.close();
} catch (IOException ignored) {
}
}
}
public static AccessTokenResponse invokeRefresh(KeycloakDeployment deployment, String refreshToken) throws IOException, HttpFailure {
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN));
@@ -17,6 +17,7 @@
*/
package org.keycloak.adapters.authorization;
import java.net.URI;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -28,8 +29,10 @@ import org.keycloak.AuthorizationContext;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.spi.HttpFacade.Request;
import org.keycloak.adapters.spi.HttpFacade.Response;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.ClientAuthorizationContext;
import org.keycloak.authorization.client.representation.ResourceRepresentation;
import org.keycloak.authorization.client.resource.ProtectedResource;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode;
@@ -53,7 +56,7 @@ public abstract class AbstractPolicyEnforcer {
this.policyEnforcer = policyEnforcer;
this.enforcerConfig = policyEnforcer.getEnforcerConfig();
this.authzClient = policyEnforcer.getClient();
this.pathMatcher = policyEnforcer.getPathMatcher();
this.pathMatcher = new PathMatcher();
this.paths = policyEnforcer.getPaths();
}
@@ -71,6 +74,7 @@ public abstract class AbstractPolicyEnforcer {
if (accessToken != null) {
Request request = httpFacade.getRequest();
Response response = httpFacade.getResponse();
String path = getPath(request);
PathConfig pathConfig = this.pathMatcher.matches(path, this.paths);
@@ -78,16 +82,11 @@ public abstract class AbstractPolicyEnforcer {
if (pathConfig == null) {
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
return createAuthorizationContext(accessToken, null);
return createAuthorizationContext(accessToken);
}
LOGGER.debugf("Could not find a configuration for path [%s]", path);
if (isDefaultAccessDeniedUri(request, enforcerConfig)) {
return createAuthorizationContext(accessToken, null);
}
handleAccessDenied(httpFacade);
response.sendError(403, "Could not find a configuration for path [" + path + "].");
return createEmptyAuthorizationContext(false);
}
@@ -96,21 +95,20 @@ public abstract class AbstractPolicyEnforcer {
return createEmptyAuthorizationContext(true);
}
Set<String> requiredScopes = getRequiredScopes(pathConfig, request);
PathConfig actualPathConfig = resolvePathConfig(pathConfig, request);
Set<String> requiredScopes = getRequiredScopes(actualPathConfig, request);
if (isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade)) {
if (isAuthorized(actualPathConfig, requiredScopes, accessToken, httpFacade)) {
try {
return createAuthorizationContext(accessToken, pathConfig);
return createAuthorizationContext(accessToken);
} catch (Exception e) {
throw new RuntimeException("Error processing path [" + pathConfig.getPath() + "].", e);
throw new RuntimeException("Error processing path [" + actualPathConfig.getPath() + "].", e);
}
}
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
if (!challenge(pathConfig, requiredScopes, httpFacade)) {
LOGGER.debugf("Challenge not sent, sending default forbidden response. Path [%s]", pathConfig);
handleAccessDenied(httpFacade);
if (!challenge(actualPathConfig, requiredScopes, httpFacade)) {
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
response.sendError(403, "Authorization failed.");
}
}
}
@@ -171,10 +169,6 @@ public abstract class AbstractPolicyEnforcer {
return false;
}
protected void handleAccessDenied(OIDCHttpFacade httpFacade) {
httpFacade.getResponse().sendError(403);
}
private boolean isDefaultAccessDeniedUri(Request request, PolicyEnforcerConfig enforcerConfig) {
String accessDeniedPath = enforcerConfig.getOnDenyRedirectTo();
@@ -204,7 +198,7 @@ public abstract class AbstractPolicyEnforcer {
}
private AuthorizationContext createEmptyAuthorizationContext(final boolean granted) {
return new ClientAuthorizationContext(authzClient) {
return new AuthorizationContext() {
@Override
public boolean hasPermission(String resourceName, String scopeName) {
return granted;
@@ -232,6 +226,32 @@ public abstract class AbstractPolicyEnforcer {
};
}
private PathConfig resolvePathConfig(PathConfig originalConfig, Request request) {
String path = getPath(request);
if (originalConfig.hasPattern()) {
ProtectedResource resource = this.authzClient.protection().resource();
Set<String> search = resource.findByFilter("uri=" + path);
if (!search.isEmpty()) {
// resource does exist on the server, cache it
ResourceRepresentation targetResource = resource.findById(search.iterator().next()).getResourceDescription();
PathConfig config = PolicyEnforcer.createPathConfig(targetResource);
config.setScopes(originalConfig.getScopes());
config.setMethods(originalConfig.getMethods());
config.setParentConfig(originalConfig);
config.setEnforcementMode(originalConfig.getEnforcementMode());
this.policyEnforcer.addPath(config);
return config;
}
}
return originalConfig;
}
private String getPath(Request request) {
return request.getRelativePath();
}
@@ -252,8 +272,8 @@ public abstract class AbstractPolicyEnforcer {
return requiredScopes;
}
private AuthorizationContext createAuthorizationContext(AccessToken accessToken, PathConfig pathConfig) {
return new ClientAuthorizationContext(accessToken, pathConfig, this.paths, authzClient);
private AuthorizationContext createAuthorizationContext(AccessToken accessToken) {
return new AuthorizationContext(accessToken, this.paths);
}
private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
@@ -17,8 +17,6 @@
*/
package org.keycloak.adapters.authorization;
import java.util.Set;
import org.jboss.logging.Logger;
import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.spi.HttpFacade;
@@ -28,6 +26,8 @@ import org.keycloak.authorization.client.resource.PermissionResource;
import org.keycloak.authorization.client.resource.ProtectionResource;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
import java.util.Set;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@@ -52,26 +52,20 @@ public class BearerTokenPolicyEnforcer extends AbstractPolicyEnforcer {
private void challengeEntitlementAuthentication(OIDCHttpFacade facade) {
HttpFacade.Response response = facade.getResponse();
AuthzClient authzClient = getAuthzClient();
String clientId = authzClient.getConfiguration().getResource();
String clientId = authzClient.getConfiguration().getClientId();
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/entitlement";
response.setStatus(401);
response.setHeader("WWW-Authenticate", "KC_ETT realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\"");
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Sending Entitlement challenge");
}
}
private void challengeUmaAuthentication(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
HttpFacade.Response response = facade.getResponse();
AuthzClient authzClient = getAuthzClient();
String ticket = getPermissionTicket(pathConfig, requiredScopes, authzClient);
String clientId = authzClient.getConfiguration().getResource();
String clientId = authzClient.getConfiguration().getClientId();
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/authorize";
response.setStatus(401);
response.setHeader("WWW-Authenticate", "UMA realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\",ticket=\"" + ticket + "\"");
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Sending UMA challenge");
}
}
private String getPermissionTicket(PathConfig pathConfig, Set<String> requiredScopes, AuthzClient authzClient) {
@@ -83,12 +83,6 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
@Override
protected boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
handleAccessDenied(facade);
return true;
}
@Override
protected void handleAccessDenied(OIDCHttpFacade facade) {
String accessDeniedPath = getEnforcerConfig().getOnDenyRedirectTo();
HttpFacade.Response response = facade.getResponse();
@@ -98,6 +92,8 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
} else {
response.sendError(403);
}
return true;
}
private AccessToken requestAuthorizationToken(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade httpFacade) {
@@ -127,7 +123,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
AccessToken token = httpFacade.getSecurityContext().getToken();
if (token.getAuthorization() == null) {
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getResource());
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getClientId());
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
} else {
EntitlementRequest request = new EntitlementRequest();
@@ -137,7 +133,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
permissionRequest.setScopes(new HashSet<>(pathConfig.getScopes()));
LOGGER.debugf("Sending entitlements request: resource_set_id [%s], resource_set_name [%s], scopes [%s].", permissionRequest.getResourceSetId(), permissionRequest.getResourceSetName(), permissionRequest.getScopes());
request.addPermission(permissionRequest);
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).get(authzClient.getConfiguration().getResource(), request);
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).get(authzClient.getConfiguration().getClientId(), request);
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
}
}
@@ -1,170 +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.
*/
package org.keycloak.adapters.authorization;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class PathCache {
/**
* The load factor.
*/
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
private final Map<String, CacheEntry> cache;
private final AtomicBoolean writing = new AtomicBoolean(false);
private final long maxAge;
/**
* Creates a new instance.
*
* @param maxEntries the maximum number of entries to keep in the cache
*/
public PathCache(int maxEntries) {
this(maxEntries, -1);
}
/**
* Creates a new instance.
*
* @param maxEntries the maximum number of entries to keep in the cache
* @param maxAge the time in milliseconds that an entry can stay in the cache. If {@code -1}, entries never expire
*/
public PathCache(final int maxEntries, long maxAge) {
cache = new LinkedHashMap<String, CacheEntry>(16, DEFAULT_LOAD_FACTOR, true) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return cache.size() > maxEntries;
}
};
this.maxAge = maxAge;
}
public void put(String uri, PathConfig newValue) {
try {
if (parkForWriteAndCheckInterrupt()) {
return;
}
CacheEntry cacheEntry = cache.get(uri);
if (cacheEntry == null) {
cache.put(uri, new CacheEntry(uri, newValue, maxAge));
}
} finally {
writing.lazySet(false);
}
}
public PathConfig get(String uri) {
if (parkForReadAndCheckInterrupt()) {
return null;
}
CacheEntry cached = cache.get(uri);
if (cached != null) {
return removeIfExpired(cached);
}
return null;
}
public void remove(String key) {
try {
if (parkForWriteAndCheckInterrupt()) {
return;
}
cache.remove(key);
} finally {
writing.lazySet(false);
}
}
private PathConfig removeIfExpired(CacheEntry cached) {
if (cached == null) {
return null;
}
if (cached.isExpired()) {
remove(cached.key());
return null;
}
return cached.value();
}
private boolean parkForWriteAndCheckInterrupt() {
while (!writing.compareAndSet(false, true)) {
LockSupport.parkNanos(1L);
if (Thread.interrupted()) {
return true;
}
}
return false;
}
private boolean parkForReadAndCheckInterrupt() {
while (writing.get()) {
LockSupport.parkNanos(1L);
if (Thread.interrupted()) {
return true;
}
}
return false;
}
private static final class CacheEntry {
final String key;
final PathConfig value;
final long expiration;
CacheEntry(String key, PathConfig value, long maxAge) {
this.key = key;
this.value = value;
if(maxAge == -1) {
expiration = -1;
} else {
expiration = System.currentTimeMillis() + maxAge;
}
}
String key() {
return key;
}
PathConfig value() {
return value;
}
boolean isExpired() {
return expiration != -1 ? System.currentTimeMillis() > expiration : false;
}
}
}
@@ -17,226 +17,93 @@
*/
package org.keycloak.adapters.authorization;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.representation.ResourceRepresentation;
import org.keycloak.authorization.client.resource.ProtectedResource;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
import java.util.Map;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
class PathMatcher {
private static final char WILDCARD = '*';
private final AuthzClient authzClient;
// TODO: make this configurable
private PathCache cache = new PathCache(100, 30000);
private static final String ANY_RESOURCE_PATTERN = "/*";
public PathMatcher(AuthzClient authzClient) {
this.authzClient = authzClient;
}
public PathConfig matches(final String targetUri, Map<String, PathConfig> paths) {
PathConfig pathConfig = paths.get(targetUri) == null ? cache.get(targetUri) : paths.get(targetUri);
PathConfig matches(final String requestedUri, Map<String, PathConfig> paths) {
PathConfig pathConfig = paths.get(requestedUri);
if (pathConfig != null) {
return pathConfig;
}
PathConfig matchingAnyPath = null;
PathConfig matchingAnySuffixPath = null;
PathConfig actualConfig = null;
for (PathConfig entry : paths.values()) {
String expectedUri = entry.getPath();
String matchingUri = null;
String protectedUri = entry.getPath();
String selectedUri = null;
if (exactMatch(expectedUri, targetUri, expectedUri)) {
matchingUri = expectedUri;
if (protectedUri.equals(ANY_RESOURCE_PATTERN) && actualConfig == null) {
selectedUri = protectedUri;
}
if (isTemplate(expectedUri)) {
String templateUri = buildUriFromTemplate(expectedUri, targetUri);
int suffixIndex = protectedUri.indexOf(ANY_RESOURCE_PATTERN + ".");
if (templateUri != null) {
if (exactMatch(expectedUri, targetUri, templateUri)) {
matchingUri = templateUri;
entry = resolvePathConfig(entry, targetUri);
}
if (suffixIndex != -1) {
String protectedSuffix = protectedUri.substring(suffixIndex + ANY_RESOURCE_PATTERN.length());
if (requestedUri.endsWith(protectedSuffix)) {
selectedUri = protectedUri;
}
}
if (matchingUri != null) {
StringBuilder path = new StringBuilder(expectedUri);
int patternIndex = path.indexOf("/" + WILDCARD);
if (protectedUri.equals(requestedUri)) {
selectedUri = protectedUri;
}
if (patternIndex != -1) {
path.delete(patternIndex, path.length());
if (protectedUri.endsWith(ANY_RESOURCE_PATTERN)) {
String formattedPattern = removeWildCardsFromUri(protectedUri);
if (!formattedPattern.equals("/") && requestedUri.startsWith(formattedPattern)) {
selectedUri = protectedUri;
}
patternIndex = path.indexOf("{");
if (patternIndex != -1) {
path.delete(patternIndex, path.length());
if (!formattedPattern.equals("/") && formattedPattern.endsWith("/") && formattedPattern.substring(0, formattedPattern.length() - 1).equals(requestedUri)) {
selectedUri = protectedUri;
}
}
String pathString = path.toString();
int startRegex = protectedUri.indexOf('{');
if ("".equals(pathString)) {
pathString = "/";
if (startRegex != -1) {
String prefix = protectedUri.substring(0, startRegex);
if (requestedUri.startsWith(prefix)) {
selectedUri = protectedUri;
}
}
if (matchingUri.equals(targetUri)) {
cache.put(targetUri, entry);
return entry;
}
if (selectedUri != null) {
selectedUri = protectedUri;
}
if (WILDCARD == expectedUri.charAt(expectedUri.length() - 1)) {
matchingAnyPath = entry;
if (selectedUri != null) {
if (actualConfig == null) {
actualConfig = entry;
} else {
int suffixIndex = expectedUri.indexOf(WILDCARD + ".");
if (actualConfig.equals(ANY_RESOURCE_PATTERN)) {
actualConfig = entry;
}
if (suffixIndex != -1) {
String protectedSuffix = expectedUri.substring(suffixIndex + 1);
if (targetUri.endsWith(protectedSuffix)) {
matchingAnySuffixPath = entry;
}
if (protectedUri.startsWith(removeWildCardsFromUri(actualConfig.getPath()))) {
actualConfig = entry;
}
}
}
}
if (matchingAnySuffixPath != null) {
cache.put(targetUri, matchingAnySuffixPath);
return matchingAnySuffixPath;
}
if (matchingAnyPath != null) {
cache.put(targetUri, matchingAnyPath);
}
return matchingAnyPath;
return actualConfig;
}
private boolean exactMatch(String expectedUri, String targetUri, String value) {
if (targetUri.equals(value)) {
return value.equals(targetUri);
}
if (endsWithWildcard(expectedUri)) {
return targetUri.startsWith(expectedUri.substring(0, expectedUri.length() - 2));
}
String suffix = "/*.";
int suffixIndex = expectedUri.indexOf(suffix);
if (suffixIndex != -1) {
return targetUri.endsWith(expectedUri.substring(suffixIndex + suffix.length() - 1));
}
return false;
}
public String buildUriFromTemplate(String expectedUri, String targetUri) {
int patternStartIndex = expectedUri.indexOf("{");
if (patternStartIndex == -1 || patternStartIndex >= targetUri.length()) {
return null;
}
if (expectedUri.split("/").length > targetUri.split("/").length) {
return null;
}
char[] expectedUriChars = expectedUri.toCharArray();
char[] matchingUri = Arrays.copyOfRange(expectedUriChars, 0, patternStartIndex);
int matchingUriLastIndex = matchingUri.length;
String targetUriParams = targetUri.substring(patternStartIndex);
if (Arrays.equals(matchingUri, Arrays.copyOf(targetUri.toCharArray(), matchingUri.length))) {
matchingUri = Arrays.copyOf(matchingUri, targetUri.length());
int paramIndex = 0;
for (int i = patternStartIndex; i < expectedUriChars.length; i++) {
if (matchingUriLastIndex >= matchingUri.length) {
break;
}
char c = expectedUriChars[i];
if (c == '{' || c == '*') {
String[] params = targetUriParams.split("/");
for (int k = paramIndex; k <= (c == '*' ? params.length : paramIndex); k++) {
if (k == params.length) {
break;
}
int paramLength = params[k].length();
if (matchingUriLastIndex + paramLength > matchingUri.length) {
return null;
}
for (int j = 0; j < paramLength; j++) {
matchingUri[matchingUriLastIndex++] = params[k].charAt(j);
}
if (c == '*' && matchingUriLastIndex < matchingUri.length) {
matchingUri[matchingUriLastIndex++] = '/';
}
}
i = expectedUri.indexOf('}', i);
} else {
if (c == '/') {
paramIndex++;
}
matchingUri[matchingUriLastIndex++] = c;
}
}
if (matchingUri[matchingUri.length - 1] == '\u0000') {
return null;
}
return String.valueOf(matchingUri);
}
return null;
}
public boolean endsWithWildcard(String expectedUri) {
return WILDCARD == expectedUri.charAt(expectedUri.length() - 1);
}
private boolean isTemplate(String uri) {
return uri.indexOf("{") != -1;
}
private PathConfig resolvePathConfig(PathConfig originalConfig, String path) {
if (originalConfig.hasPattern()) {
ProtectedResource resource = this.authzClient.protection().resource();
Set<String> search = resource.findByFilter("uri=" + path);
if (!search.isEmpty()) {
// resource does exist on the server, cache it
ResourceRepresentation targetResource = resource.findById(search.iterator().next()).getResourceDescription();
PathConfig config = PolicyEnforcer.createPathConfig(targetResource);
config.setScopes(originalConfig.getScopes());
config.setMethods(originalConfig.getMethods());
config.setParentConfig(originalConfig);
config.setEnforcementMode(originalConfig.getEnforcementMode());
return config;
}
}
return originalConfig;
private String removeWildCardsFromUri(String protectedUri) {
return protectedUri.replaceAll("/[*]", "/");
}
}
@@ -21,9 +21,7 @@ import org.jboss.logging.Logger;
import org.keycloak.AuthorizationContext;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.ClientAuthenticator;
import org.keycloak.authorization.client.Configuration;
import org.keycloak.authorization.client.representation.RegistrationResponse;
import org.keycloak.authorization.client.representation.ResourceRepresentation;
@@ -53,18 +51,11 @@ public class PolicyEnforcer {
private final AuthzClient authzClient;
private final PolicyEnforcerConfig enforcerConfig;
private final Map<String, PathConfig> paths;
private final PathMatcher pathMatcher;
public PolicyEnforcer(KeycloakDeployment deployment, AdapterConfig adapterConfig) {
this.deployment = deployment;
this.enforcerConfig = adapterConfig.getPolicyEnforcerConfig();
this.authzClient = AuthzClient.create(new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), deployment.getClient()), new ClientAuthenticator() {
@Override
public void configureClientCredentials(HashMap<String, String> requestParams, HashMap<String, String> requestHeaders) {
ClientCredentialsProviderUtils.setClientCredentials(PolicyEnforcer.this.deployment, requestHeaders, requestParams);
}
});
this.pathMatcher = new PathMatcher(this.authzClient);
this.authzClient = AuthzClient.create(new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), deployment.getClient()));
this.paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig);
if (LOGGER.isDebugEnabled()) {
@@ -227,14 +218,7 @@ public class PolicyEnforcer {
pathConfig.setId(resourceDescription.getId());
pathConfig.setName(resourceDescription.getName());
String uri = resourceDescription.getUri();
if (uri == null || "".equals(uri.trim())) {
throw new RuntimeException("Failed to configure paths. Resource [" + resourceDescription.getName() + "] has an invalid or empty URI [" + uri + "].");
}
pathConfig.setPath(uri);
pathConfig.setPath(resourceDescription.getUri());
List<String> scopeNames = new ArrayList<>();
@@ -247,8 +231,4 @@ public class PolicyEnforcer {
return pathConfig;
}
public PathMatcher getPathMatcher() {
return pathMatcher;
}
}
@@ -29,13 +29,10 @@ import org.keycloak.common.util.PemUtils;
import org.keycloak.enums.TokenStore;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
*/
public class KeycloakDeploymentBuilderTest {
@@ -56,12 +53,10 @@ public class KeycloakDeploymentBuilderTest {
assertEquals(1000, deployment.getCorsMaxAge());
assertEquals("POST, PUT, DELETE, GET", deployment.getCorsAllowedMethods());
assertEquals("X-Custom, X-Custom2", deployment.getCorsAllowedHeaders());
assertEquals("X-Custom3, X-Custom4", deployment.getCorsExposedHeaders());
assertTrue(deployment.isBearerOnly());
assertTrue(deployment.isPublicClient());
assertTrue(deployment.isEnableBasicAuth());
assertTrue(deployment.isExposeToken());
assertFalse(deployment.isOAuthQueryParameterEnabled());
assertEquals("234234-234234-234234", deployment.getResourceCredentials().get("secret"));
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
assertEquals(20, ((ThreadSafeClientConnManager) deployment.getClient().getConnectionManager()).getMaxTotal());
@@ -75,7 +70,6 @@ public class KeycloakDeploymentBuilderTest {
assertEquals(10, deployment.getTokenMinimumTimeToLive());
assertEquals(20, deployment.getMinTimeBetweenJwksRequests());
assertEquals(120, deployment.getPublicKeyCacheTtl());
assertEquals("/api/$1", deployment.getRedirectRewriteRules().get("^/wsmaster/api/(.*)$"));
}
@Test
@@ -1,49 +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.
*/
package org.keycloak.adapters;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
*/
public class KeycloakDeploymentTest {
@Test
public void shouldNotEnableOAuthQueryParamWhenIgnoreIsTrue() {
KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
keycloakDeployment.setIgnoreOAuthQueryParameter(true);
assertFalse(keycloakDeployment.isOAuthQueryParameterEnabled());
}
@Test
public void shouldEnableOAuthQueryParamWhenIgnoreIsFalse() {
KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
keycloakDeployment.setIgnoreOAuthQueryParameter(false);
assertTrue(keycloakDeployment.isOAuthQueryParameterEnabled());
}
@Test
public void shouldEnableOAuthQueryParamWhenIgnoreNotSet() {
KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
assertTrue(keycloakDeployment.isOAuthQueryParameterEnabled());
}
}
@@ -9,7 +9,6 @@
"cors-max-age": 1000,
"cors-allowed-methods": "POST, PUT, DELETE, GET",
"cors-allowed-headers": "X-Custom, X-Custom2",
"cors-exposed-headers": "X-Custom3, X-Custom4",
"bearer-only": true,
"public-client": true,
"enable-basic-auth": true,
@@ -32,9 +31,5 @@
"principal-attribute": "email",
"token-minimum-time-to-live": 10,
"min-time-between-jwks-requests": 20,
"public-key-cache-ttl": 120,
"ignore-oauth-query-parameter": true,
"redirect-rewrite-rules" : {
"^/wsmaster/api/(.*)$" : "/api/$1"
}
"public-key-cache-ttl": 120
}
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-as7-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-as7-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+3 -1
View File
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-as7-integration-pom</artifactId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -88,6 +88,7 @@
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>3.1.0.GA</version>
</dependency>
<dependency>
@@ -103,6 +104,7 @@ projects that depend on this project.-->
<dependency>
<groupId>org.jboss.msc</groupId>
<artifactId>jboss-msc</artifactId>
<version>1.0.2.GA</version>
</dependency>
<dependency>
+1 -13
View File
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak AS7 / JBoss EAP 6 Integration</name>
@@ -30,18 +30,6 @@
<artifactId>keycloak-as7-integration-pom</artifactId>
<packaging>pom</packaging>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jboss.as</groupId>
<artifactId>jboss-as-parent</artifactId>
<version>${jboss.as.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<modules>
<module>as7-adapter-spi</module>
<module>as7-adapter</module>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+3 -16
View File
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak Jetty Integration</name>
@@ -33,22 +33,9 @@
<modules>
<module>jetty-core</module>
<module>jetty8.1</module>
<module>jetty9.1</module>
<module>jetty9.2</module>
<module>jetty9.3</module>
<module>jetty9.4</module>
</modules>
<profiles>
<profile>
<id>community</id>
<activation>
<property>
<name>!product</name>
</property>
</activation>
<modules>
<module>jetty9.1</module>
<module>jetty9.4</module>
</modules>
</profile>
</profiles>
</project>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
-59
View File
@@ -1,59 +0,0 @@
/*
* MIT License
*
* Copyright 2017 Brett Epps <https://github.com/eppsilon>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import * as Keycloak from 'keycloak';
export as namespace KeycloakAuthorization;
export = KeycloakAuthorization;
/**
* Creates a new Keycloak client instance.
* @param config Path to a JSON config file or a plain config object.
*/
declare function KeycloakAuthorization(keycloak: Keycloak.KeycloakInstance): KeycloakAuthorization.KeycloakAuthorizationInstance;
declare namespace KeycloakAuthorization {
interface KeycloakAuthorizationPromise {
then(onGrant: (rpt: string) => void, onDeny: () => void, onError: () => void): void;
}
interface KeycloakAuthorizationInstance {
rpt: any;
config: { rpt_endpoint: string };
init(): void;
/**
* This method enables client applications to better integrate with resource servers protected by a Keycloak
* policy enforcer.
*
* In this case, the resource server will respond with a 401 status code and a WWW-Authenticate header holding the
* necessary information to ask a Keycloak server for authorization data using both UMA and Entitlement protocol,
* depending on how the policy enforcer at the resource server was configured.
*/
authorize(wwwAuthenticateHeader: string): KeycloakAuthorizationPromise;
/**
* Obtains all entitlements from a Keycloak server based on a given resourceServerId.
*/
entitlement(resourceServerId: string, entitlementRequest: {}): KeycloakAuthorizationPromise;
}
}
-478
View File
@@ -1,478 +0,0 @@
/*
* MIT License
*
* Copyright 2017 Brett Epps <https://github.com/eppsilon>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
export as namespace Keycloak;
export = Keycloak;
/**
* Creates a new Keycloak client instance.
* @param config Path to a JSON config file or a plain config object.
*/
declare function Keycloak(config?: string|{}): Keycloak.KeycloakInstance;
declare namespace Keycloak {
type KeycloakAdapterName = 'cordova'|'default';
type KeycloakOnLoad = 'login-required'|'check-sso';
type KeycloakResponseMode = 'query'|'fragment';
type KeycloakResponseType = 'code'|'id_token token'|'code id_token token';
type KeycloakFlow = 'standard'|'implicit'|'hybrid';
interface KeycloakInitOptions {
/**
* @private Undocumented.
*/
adapter?: KeycloakAdapterName;
/**
* Specifies an action to do on load.
*/
onLoad?: KeycloakOnLoad;
/**
* Set an initial value for the token.
*/
token?: string;
/**
* Set an initial value for the refresh token.
*/
refreshToken?: string;
/**
* Set an initial value for the id token (only together with `token` or
* `refreshToken`).
*/
idToken?: string;
/**
* Set an initial value for skew between local time and Keycloak server in
* seconds (only together with `token` or `refreshToken`).
*/
timeSkew?: number;
/**
* Set to enable/disable monitoring login state.
* @default true
*/
checkLoginIframe?: boolean;
/**
* Set the interval to check login state (in seconds).
* @default 5
*/
checkLoginIframeInterval?: boolean;
/**
* Set the OpenID Connect response mode to send to Keycloak upon login.
* @default fragment After successful authentication Keycloak will redirect
* to JavaScript application with OpenID Connect parameters
* added in URL fragment. This is generally safer and
* recommended over query.
*/
responseMode?: KeycloakResponseMode;
/**
* Set the OpenID Connect flow.
* @default standard
*/
flow?: KeycloakFlow;
}
interface KeycloakLoginOptions {
/**
* @private Undocumented.
*/
scope?: string;
/**
* Specifies the uri to redirect to after login.
*/
redirectUri?: string;
/**
* By default the login screen is displayed if the user is not logged into
* Keycloak. To only authenticate to the application if the user is already
* logged in and not display the login page if the user is not logged in, set
* this option to `'none'`. To always require re-authentication and ignore
* SSO, set this option to `'login'`.
*/
prompt?: 'none'|'login';
/**
* If value is `'register'` then user is redirected to registration page,
* otherwise to login page.
*/
action?: 'register';
/**
* Used just if user is already authenticated. Specifies maximum time since
* the authentication of user happened. If user is already authenticated for
* longer time than `'maxAge'`, the SSO is ignored and he will need to
* authenticate again.
*/
maxAge?: number;
/**
* Used to pre-fill the username/email field on the login form.
*/
loginHint?: string;
/**
* Used to tell Keycloak which IDP the user wants to authenticate with.
*/
idpHint?: string;
/**
* Specifies the desired locale for the UI.
*/
locale?: string;
}
type KeycloakPromiseCallback<T> = (result: T) => void;
interface KeycloakPromise<TSuccess, TError> {
/**
* Function to call if the promised action succeeds.
*/
success(callback: KeycloakPromiseCallback<TSuccess>): KeycloakPromise<TSuccess, TError>;
/**
* Function to call if the promised action throws an error.
*/
error(callback: KeycloakPromiseCallback<TError>): KeycloakPromise<TSuccess, TError>;
}
interface KeycloakError {
error: string;
error_description: string;
}
interface KeycloakAdapter {
login(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
logout(options?: any): KeycloakPromise<void, void>;
register(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
accountManagement(): KeycloakPromise<void, void>;
redirectUri(options: { redirectUri: string; }, encodeHash: boolean): string;
}
interface KeycloakProfile {
id?: string;
username?: string;
email?: string;
firstName?: string;
lastName?: string;
enabled?: boolean;
emailVerified?: boolean;
totp?: boolean;
createdTimestamp?: number;
}
// export interface KeycloakUserInfo {}
/**
* A client for the Keycloak authentication server.
* @see {@link https://keycloak.gitbooks.io/securing-client-applications-guide/content/topics/oidc/javascript-adapter.html|Keycloak JS adapter documentation}
*/
interface KeycloakInstance {
/**
* Is true if the user is authenticated, false otherwise.
*/
authenticated?: boolean;
/**
* The user id.
*/
subject?: string;
/**
* Response mode passed in init (default value is `'fragment'`).
*/
responseMode?: KeycloakResponseMode;
/**
* Response type sent to Keycloak with login requests. This is determined
* based on the flow value used during initialization, but can be overridden
* by setting this value.
*/
responseType?: KeycloakResponseType;
/**
* Flow passed in init.
*/
flow?: KeycloakFlow;
/**
* The realm roles associated with the token.
*/
realmAccess?: { roles: string[] };
/**
* The resource roles associated with the token.
*/
resourceAccess?: string[];
/**
* The base64 encoded token that can be sent in the Authorization header in
* requests to services.
*/
token?: string;
/**
* The parsed token as a JavaScript object.
*/
tokenParsed?: {
exp?: number;
iat?: number;
nonce?: string;
sub?: string;
session_state?: string;
realm_access?: { roles: string[] };
resource_access?: string[];
};
/**
* The base64 encoded refresh token that can be used to retrieve a new token.
*/
refreshToken?: string;
/**
* The parsed refresh token as a JavaScript object.
*/
refreshTokenParsed?: { nonce?: string };
/**
* The base64 encoded ID token.
*/
idToken?: string;
/**
* The parsed id token as a JavaScript object.
*/
idTokenParsed?: { nonce?: string };
/**
* The estimated time difference between the browser time and the Keycloak
* server in seconds. This value is just an estimation, but is accurate
* enough when determining if a token is expired or not.
*/
timeSkew?: number;
/**
* @private Undocumented.
*/
loginRequired?: boolean;
/**
* @private Undocumented.
*/
authServerUrl?: string;
/**
* @private Undocumented.
*/
realm?: string;
/**
* @private Undocumented.
*/
clientId?: string;
/**
* @private Undocumented.
*/
clientSecret?: string;
/**
* @private Undocumented.
*/
redirectUri?: string;
/**
* @private Undocumented.
*/
sessionId?: string;
/**
* @private Undocumented.
*/
profile?: KeycloakProfile;
/**
* @private Undocumented.
*/
userInfo?: {}; // KeycloakUserInfo;
/**
* Called when the adapter is initialized.
*/
onReady?(authenticated?: boolean): void;
/**
* Called when a user is successfully authenticated.
*/
onAuthSuccess?(): void;
/**
* Called if there was an error during authentication.
*/
onAuthError?(errorData: KeycloakError): void;
/**
* Called when the token is refreshed.
*/
onAuthRefreshSuccess?(): void;
/**
* Called if there was an error while trying to refresh the token.
*/
onAuthRefreshError?(): void;
/**
* Called if the user is logged out (will only be called if the session
* status iframe is enabled, or in Cordova mode).
*/
onAuthLogout?(): void;
/**
* Called when the access token is expired. If a refresh token is available
* the token can be refreshed with Keycloak#updateToken, or in cases where
* it's not (ie. with implicit flow) you can redirect to login screen to
* obtain a new access token.
*/
onTokenExpired?(): void;
/**
* Called to initialize the adapter.
* @param initOptions Initialization options.
* @returns A promise to set functions to be invoked on success or error.
*/
init(initOptions: KeycloakInitOptions): KeycloakPromise<boolean, KeycloakError>;
/**
* Redirects to login form.
* @param options Login options.
*/
login(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
/**
* Redirects to logout.
* @param options Logout options.
* @param options.redirectUri Specifies the uri to redirect to after logout.
*/
logout(options?: any): KeycloakPromise<void, void>;
/**
* Redirects to registration form.
* @param options Supports same options as Keycloak#login but `action` is
* set to `'register'`.
*/
register(options?: any): KeycloakPromise<void, void>;
/**
* Redirects to the Account Management Console.
*/
accountManagement(): KeycloakPromise<void, void>;
/**
* Returns the URL to login form.
* @param options Supports same options as Keycloak#login.
*/
createLoginUrl(options?: KeycloakLoginOptions): string;
/**
* Returns the URL to logout the user.
* @param options Logout options.
* @param options.redirectUri Specifies the uri to redirect to after logout.
*/
createLogoutUrl(options?: any): string;
/**
* Returns the URL to registration page.
* @param options Supports same options as Keycloak#createLoginUrl but
* `action` is set to `'register'`.
*/
createRegisterUrl(options?: KeycloakLoginOptions): string;
/**
* Returns the URL to the Account Management Console.
*/
createAccountUrl(): string;
/**
* Returns true if the token has less than `minValidity` seconds left before
* it expires.
* @param minValidity If not specified, `0` is used.
*/
isTokenExpired(minValidity?: number): boolean;
/**
* If the token expires within `minValidity` seconds, the token is refreshed.
* If the session status iframe is enabled, the session status is also
* checked.
* @returns A promise to set functions that can be invoked if the token is
* still valid, or if the token is no longer valid.
* @example
* ```js
* keycloak.updateToken(5).success(function(refreshed) {
* if (refreshed) {
* alert('Token was successfully refreshed');
* } else {
* alert('Token is still valid');
* }
* }).error(function() {
* alert('Failed to refresh the token, or the session has expired');
* });
*/
updateToken(minValidity: number): KeycloakPromise<boolean, boolean>;
/**
* Clears authentication state, including tokens. This can be useful if
* the application has detected the session was expired, for example if
* updating token fails. Invoking this results in Keycloak#onAuthLogout
* callback listener being invoked.
*/
clearToken(): void;
/**
* Returns true if the token has the given realm role.
* @param role A realm role name.
*/
hasRealmRole(role: string): boolean;
/**
* Returns true if the token has the given role for the resource.
* @param role A role name.
* @param resource If not specified, `clientId` is used.
*/
hasResourceRole(role: string, resource?: string): boolean;
/**
* Loads the user's profile.
* @returns A promise to set functions to be invoked on success or error.
*/
loadUserProfile(): KeycloakPromise<KeycloakProfile, void>;
/**
* @private Undocumented.
*/
loadUserInfo(): KeycloakPromise<{}, void>;
}
}
@@ -157,7 +157,7 @@
processCallback(callback, initPromise);
return;
} else if (initOptions) {
if (initOptions.token && initOptions.refreshToken) {
if (initOptions.refreshToken) {
setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken);
if (loginIframe.enable) {
@@ -587,7 +587,7 @@
req.onreadystatechange = function () {
if (req.readyState == 4) {
if (req.status == 200 || fileLoaded(req)) {
if (req.status == 200) {
var config = JSON.parse(req.responseText);
kc.authServerUrl = config['auth-server-url'];
@@ -633,10 +633,6 @@
return promise.promise;
}
function fileLoaded(xhr) {
return xhr.status == 0 && xhr.responseText && xhr.responseURL.startsWith('file:');
}
function setToken(token, refreshToken, idToken, timeLocal) {
if (kc.tokenTimeoutHandle) {
clearTimeout(kc.tokenTimeoutHandle);
@@ -836,16 +832,11 @@
document.body.appendChild(iframe);
var messageCallback = function(event) {
if ((event.origin !== loginIframe.iframeOrigin) || (loginIframe.iframe.contentWindow !== event.source)) {
if (event.origin !== loginIframe.iframeOrigin) {
return;
}
if (!(event.data == 'unchanged' || event.data == 'changed' || event.data == 'error')) {
return;
}
if (event.data != 'unchanged') {
if (event.data != "unchanged") {
kc.clearToken();
}
@@ -853,7 +844,7 @@
for (var i = callbacks.length - 1; i >= 0; --i) {
var promise = callbacks[i];
if (event.data == 'unchanged') {
if (event.data == "unchanged") {
promise.setSuccess();
} else {
promise.setError();
@@ -1232,7 +1223,7 @@
break;
default:
if (responseMode != 'query' || !handleQueryParam(param, queryParams[param], oauth)) {
oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + param + '=' + encodeURIComponent(queryParams[param]);
oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + param + '=' + queryParams[param];
}
break;
}
@@ -0,0 +1,49 @@
<!--
~ 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.
-->
<script>
function getCookie(cname)
{
var name = cname + "=";
var ca = document.cookie.split(';');
for(var i=0; i<ca.length; i++)
{
var c = ca[i].trim();
if (c.indexOf(name)==0) return c.substring(name.length,c.length);
}
return null;
}
function receiveMessage(event)
{
if (event.origin !== "ORIGIN") {
console.log(event.origin + " does not match built origin");
return;
}
var data = JSON.parse(event.data);
data.loggedIn = false;
var cookie = getCookie('KEYCLOAK_SESSION');
if (cookie) {
data.loggedIn = true;
data.session = cookie;
}
event.source.postMessage(JSON.stringify(data),
event.origin);
}
window.addEventListener("message", receiveMessage, false);
</script>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -3
View File
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<name>Keycloak OIDC Client Adapter Modules</name>
@@ -41,11 +41,9 @@
<module>servlet-filter</module>
<module>servlet-oauth-client</module>
<module>spring-boot</module>
<module>spring-boot-container-bundle</module>
<module>spring-security</module>
<module>tomcat</module>
<module>undertow</module>
<module>wildfly</module>
<module>wildfly-elytron</module>
</modules>
</project>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -54,96 +54,72 @@ import java.util.regex.Pattern;
*/
public class KeycloakOIDCFilter implements Filter {
private final static Logger log = Logger.getLogger("" + KeycloakOIDCFilter.class);
public static final String SKIP_PATTERN_PARAM = "keycloak.config.skipPattern";
public static final String CONFIG_RESOLVER_PARAM = "keycloak.config.resolver";
public static final String CONFIG_FILE_PARAM = "keycloak.config.file";
public static final String CONFIG_PATH_PARAM = "keycloak.config.path";
protected AdapterDeploymentContext deploymentContext;
protected SessionIdMapper idMapper = new InMemorySessionIdMapper();
protected NodesRegistrationManagement nodesRegistrationManagement;
protected Pattern skipPattern;
private final KeycloakConfigResolver definedconfigResolver;
/**
* Constructor that can be used to define a {@code KeycloakConfigResolver} that will be used at initialization to
* provide the {@code KeycloakDeployment}.
* @param definedconfigResolver the resolver
*/
public KeycloakOIDCFilter(KeycloakConfigResolver definedconfigResolver) {
this.definedconfigResolver = definedconfigResolver;
}
public KeycloakOIDCFilter() {
this(null);
}
private final static Logger log = Logger.getLogger(""+KeycloakOIDCFilter.class);
@Override
public void init(final FilterConfig filterConfig) throws ServletException {
String skipPatternDefinition = filterConfig.getInitParameter(SKIP_PATTERN_PARAM);
if (skipPatternDefinition != null) {
skipPattern = Pattern.compile(skipPatternDefinition, Pattern.DOTALL);
}
if (definedconfigResolver != null) {
deploymentContext = new AdapterDeploymentContext(definedconfigResolver);
log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", definedconfigResolver.getClass());
String configResolverClass = filterConfig.getInitParameter("keycloak.config.resolver");
if (configResolverClass != null) {
try {
KeycloakConfigResolver configResolver = (KeycloakConfigResolver) getClass().getClassLoader().loadClass(configResolverClass).newInstance();
deploymentContext = new AdapterDeploymentContext(configResolver);
log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass);
} catch (Exception ex) {
log.log(Level.FINE, "The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", new Object[]{configResolverClass, ex.getMessage()});
deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
}
} else {
String configResolverClass = filterConfig.getInitParameter(CONFIG_RESOLVER_PARAM);
if (configResolverClass != null) {
String fp = filterConfig.getInitParameter("keycloak.config.file");
InputStream is = null;
if (fp != null) {
try {
KeycloakConfigResolver configResolver = (KeycloakConfigResolver) getClass().getClassLoader().loadClass(configResolverClass).newInstance();
deploymentContext = new AdapterDeploymentContext(configResolver);
log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass);
} catch (Exception ex) {
log.log(Level.FINE, "The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", new Object[]{configResolverClass, ex.getMessage()});
deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
is = new FileInputStream(fp);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
} else {
String fp = filterConfig.getInitParameter(CONFIG_FILE_PARAM);
InputStream is = null;
if (fp != null) {
try {
is = new FileInputStream(fp);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
} else {
String path = "/WEB-INF/keycloak.json";
String pathParam = filterConfig.getInitParameter(CONFIG_PATH_PARAM);
if (pathParam != null) path = pathParam;
is = filterConfig.getServletContext().getResourceAsStream(path);
}
KeycloakDeployment kd = createKeycloakDeploymentFrom(is);
deploymentContext = new AdapterDeploymentContext(kd);
log.fine("Keycloak is using a per-deployment configuration.");
String path = "/WEB-INF/keycloak.json";
String pathParam = filterConfig.getInitParameter("keycloak.config.path");
if (pathParam != null) path = pathParam;
is = filterConfig.getServletContext().getResourceAsStream(path);
}
KeycloakDeployment kd = createKeycloakDeploymentFrom(is);
deploymentContext = new AdapterDeploymentContext(kd);
log.fine("Keycloak is using a per-deployment configuration.");
}
filterConfig.getServletContext().setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
nodesRegistrationManagement = new NodesRegistrationManagement();
}
private KeycloakDeployment createKeycloakDeploymentFrom(InputStream is) {
if (is == null) {
log.fine("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
return new KeycloakDeployment();
}
return KeycloakDeploymentBuilder.build(is);
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
log.fine("Keycloak OIDC Filter");
//System.err.println("Keycloak OIDC Filter: " + ((HttpServletRequest)req).getRequestURL().toString());
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
@@ -225,7 +201,7 @@ public class KeycloakOIDCFilter implements Filter {
*
* @param request the request to check
* @return {@code true} if the request should not be handled,
* {@code false} otherwise.
* {@code false} otherwise.
*/
private boolean shouldSkip(HttpServletRequest request) {
@@ -168,7 +168,7 @@ public class OIDCFilterSessionStore extends FilterSessionStore implements Adapte
HttpSession httpSession = request.getSession();
httpSession.setAttribute(KeycloakAccount.class.getName(), sAccount);
httpSession.setAttribute(KeycloakSecurityContext.class.getName(), sAccount.getKeycloakSecurityContext());
if (idMapper != null) idMapper.map(account.getKeycloakSecurityContext().getToken().getSessionState(), account.getPrincipal().getName(), httpSession.getId());
if (idMapper != null) idMapper.map(account.getKeycloakSecurityContext().getToken().getClientSession(), account.getPrincipal().getName(), httpSession.getId());
//String username = securityContext.getToken().getSubject();
//log.fine("userSessionManagement.login: " + username);
}
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -41,60 +41,12 @@ import java.io.InputStream;
import java.net.URI;
import java.util.List;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Base64Url;
import java.security.MessageDigest;
import java.security.SecureRandom;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
// https://tools.ietf.org/html/rfc7636#section-4
private String codeVerifier;
private String codeChallenge;
private String codeChallengeMethod = OAuth2Constants.PKCE_METHOD_S256;
private static Logger logger = Logger.getLogger(ServletOAuthClient.class);
public static String generateSecret() {
return generateSecret(32);
}
public static String generateSecret(int bytes) {
byte[] buf = new byte[bytes];
new SecureRandom().nextBytes(buf);
return Base64Url.encode(buf);
}
private void setCodeVerifier() {
codeVerifier = generateSecret();
logger.debugf("Generated codeVerifier = %s", codeVerifier);
return;
}
private void setCodeChallenge() {
try {
if (codeChallengeMethod.equals(OAuth2Constants.PKCE_METHOD_S256)) {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(codeVerifier.getBytes());
StringBuilder sb = new StringBuilder();
for (byte b : md.digest()) {
String hex = String.format("%02x", b);
sb.append(hex);
}
codeChallenge = Base64Url.encode(sb.toString().getBytes());
} else {
codeChallenge = Base64Url.encode(codeVerifier.getBytes());
}
logger.debugf("Encode codeChallenge = %s, codeChallengeMethod = %s", codeChallenge, codeChallengeMethod);
} catch (Exception e) {
logger.info("PKCE client side unknown hash algorithm");
codeChallenge = Base64Url.encode(codeVerifier.getBytes());
}
}
/**
* closes client
*/
@@ -105,15 +57,7 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
private AccessTokenResponse resolveBearerToken(HttpServletRequest request, String redirectUri, String code) throws IOException, ServerRequest.HttpFailure {
// Don't send sessionId in oauth clients for now
KeycloakDeployment resolvedDeployment = resolveDeployment(getDeployment(), request);
// https://tools.ietf.org/html/rfc7636#section-4
if (codeVerifier != null) {
logger.debugf("Before sending Token Request, codeVerifier = %s", codeVerifier);
return ServerRequest.invokeAccessCodeToToken(resolvedDeployment, code, redirectUri, null, codeVerifier);
} else {
logger.debug("Before sending Token Request without codeVerifier");
return ServerRequest.invokeAccessCodeToToken(resolvedDeployment, code, redirectUri, null);
}
return ServerRequest.invokeAccessCodeToToken(resolvedDeployment, code, redirectUri, null);
}
/**
@@ -150,12 +94,6 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
String authUrl = resolvedDeployment.getAuthUrl().clone().build().toString();
String scopeParam = TokenUtil.attachOIDCScope(scope);
// https://tools.ietf.org/html/rfc7636#section-4
if (resolvedDeployment.isPkce()) {
setCodeVerifier();
setCodeChallenge();
}
KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(authUrl)
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
.queryParam(OAuth2Constants.CLIENT_ID, getClientId())
@@ -1,62 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<artifactId>spring-boot-container-bundle</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-tomcat8-adapter</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-undertow-adapter</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-jetty93-adapter</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<includes>
<include>org.keycloak:keycloak-tomcat8-adapter</include>
<include>org.keycloak:keycloak-undertow-adapter</include>
<include>org.keycloak:keycloak-jetty93-adapter</include>
<include>org.keycloak:keycloak-tomcat-core-adapter</include>
<include>org.keycloak:keycloak-tomcat-adapter-spi</include>
<include>org.keycloak:keycloak-undertow-adapter</include>
<include>org.keycloak:keycloak-undertow-adapter-spi</include>
<include>org.keycloak:keycloak-jetty-core</include>
<include>org.keycloak:keycloak-jetty-adapter-spi</include>
</includes>
</artifactSet>
<createSourcesJar>true</createSourcesJar>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
+44 -29
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -43,61 +43,75 @@
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>spring-boot-container-bundle</artifactId>
<version>${project.version}</version>
<optional>true</optional>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-security-adapter</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-tomcat8-adapter</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-undertow-adapter</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-jetty92-adapter</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-servlet</artifactId>
<scope>compile</scope>
<optional>true</optional>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<optional>true</optional>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<optional>true</optional>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<optional>true</optional>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-servlet</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-undertow-adapter-spi</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
@@ -111,10 +125,11 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -33,7 +33,6 @@ import org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve;
import org.keycloak.adapters.undertow.KeycloakServletExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
@@ -61,8 +60,7 @@ import java.util.Set;
@Configuration
@ConditionalOnWebApplication
@EnableConfigurationProperties(KeycloakSpringBootProperties.class)
@ConditionalOnProperty(value = "keycloak.enabled", matchIfMissing = true)
public class KeycloakAutoConfiguration {
public class KeycloakSpringBootConfiguration {
private KeycloakSpringBootProperties keycloakProperties;
@@ -72,7 +70,6 @@ public class KeycloakAutoConfiguration {
KeycloakSpringBootConfigResolver.setAdapterConfig(keycloakProperties);
}
@Bean
public EmbeddedServletContainerCustomizer getKeycloakContainerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
@@ -144,11 +141,11 @@ public class KeycloakAutoConfiguration {
List<io.undertow.servlet.api.SecurityConstraint> undertowSecurityConstraints = new ArrayList<io.undertow.servlet.api.SecurityConstraint>();
for (KeycloakSpringBootProperties.SecurityConstraint constraintDefinition : keycloakProperties.getSecurityConstraints()) {
io.undertow.servlet.api.SecurityConstraint undertowSecurityConstraint = new io.undertow.servlet.api.SecurityConstraint();
undertowSecurityConstraint.addRolesAllowed(constraintDefinition.getAuthRoles());
for (KeycloakSpringBootProperties.SecurityCollection collectionDefinition : constraintDefinition.getSecurityCollections()) {
io.undertow.servlet.api.SecurityConstraint undertowSecurityConstraint = new io.undertow.servlet.api.SecurityConstraint();
undertowSecurityConstraint.addRolesAllowed(collectionDefinition.getAuthRoles());
WebResourceCollection webResourceCollection = new WebResourceCollection();
webResourceCollection.addHttpMethods(collectionDefinition.getMethods());
webResourceCollection.addHttpMethodOmissions(collectionDefinition.getOmittedMethods());
@@ -156,9 +153,8 @@ public class KeycloakAutoConfiguration {
undertowSecurityConstraint.addWebResourceCollections(webResourceCollection);
undertowSecurityConstraints.add(undertowSecurityConstraint);
}
undertowSecurityConstraints.add(undertowSecurityConstraint);
}
return undertowSecurityConstraints;
}
@@ -178,70 +174,42 @@ public class KeycloakAutoConfiguration {
KeycloakJettyAuthenticator keycloakJettyAuthenticator = new KeycloakJettyAuthenticator();
keycloakJettyAuthenticator.setConfigResolver(new KeycloakSpringBootConfigResolver());
/* see org.eclipse.jetty.webapp.StandardDescriptorProcessor#visitSecurityConstraint for an example
on how to map servlet spec to Constraints */
List<ConstraintMapping> jettyConstraintMappings = new ArrayList<ConstraintMapping>();
for (KeycloakSpringBootProperties.SecurityConstraint constraintDefinition : keycloakProperties.getSecurityConstraints()) {
for (KeycloakSpringBootProperties.SecurityCollection securityCollectionDefinition : constraintDefinition
.getSecurityCollections()) {
// securityCollection matches servlet spec's web-resource-collection
Constraint jettyConstraint = new Constraint();
if (constraintDefinition.getAuthRoles().size() > 0) {
jettyConstraint.setAuthenticate(true);
jettyConstraint.setRoles(constraintDefinition.getAuthRoles().toArray(new String[0]));
}
jettyConstraint.setName(securityCollectionDefinition.getName());
jettyConstraint.setAuthenticate(true);
// according to the servlet spec each security-constraint has at least one URL pattern
for(String pattern : securityCollectionDefinition.getPatterns()) {
/* the following code is asymmetric as Jetty's ConstraintMapping accepts only one allowed HTTP method,
but multiple omitted methods. Therefore we add one ConstraintMapping for each allowed
mapping but only one mapping in the cases of omitted methods or no methods.
*/
if (securityCollectionDefinition.getMethods().size() > 0) {
// according to the servlet spec we have either methods ...
for(String method : securityCollectionDefinition.getMethods()) {
ConstraintMapping jettyConstraintMapping = new ConstraintMapping();
jettyConstraintMappings.add(jettyConstraintMapping);
jettyConstraintMapping.setConstraint(jettyConstraint);
jettyConstraintMapping.setPathSpec(pattern);
jettyConstraintMapping.setMethod(method);
}
} else if (securityCollectionDefinition.getOmittedMethods().size() > 0){
// ... omitted methods ...
ConstraintMapping jettyConstraintMapping = new ConstraintMapping();
jettyConstraintMappings.add(jettyConstraintMapping);
jettyConstraintMapping.setConstraint(jettyConstraint);
jettyConstraintMapping.setPathSpec(pattern);
jettyConstraintMapping.setMethodOmissions(
securityCollectionDefinition.getOmittedMethods().toArray(new String[0]));
} else {
// ... or no methods at all
ConstraintMapping jettyConstraintMapping = new ConstraintMapping();
jettyConstraintMappings.add(jettyConstraintMapping);
jettyConstraintMapping.setConstraint(jettyConstraint);
jettyConstraintMapping.setPathSpec(pattern);
}
if (securityCollectionDefinition.getName() != null) {
jettyConstraint.setName(securityCollectionDefinition.getName());
}
jettyConstraint.setRoles(securityCollectionDefinition.getAuthRoles().toArray(new String[0]));
ConstraintMapping jettyConstraintMapping = new ConstraintMapping();
if (securityCollectionDefinition.getPatterns().size() > 0) {
//First pattern wins
jettyConstraintMapping.setPathSpec(securityCollectionDefinition.getPatterns().get(0));
jettyConstraintMapping.setConstraint(jettyConstraint);
}
if (securityCollectionDefinition.getMethods().size() > 0) {
//First method wins
jettyConstraintMapping.setMethod(securityCollectionDefinition.getMethods().get(0));
}
jettyConstraintMapping.setMethodOmissions(
securityCollectionDefinition.getOmittedMethods().toArray(new String[0]));
jettyConstraintMappings.add(jettyConstraintMapping);
}
}
WebAppContext webAppContext = server.getBean(WebAppContext.class);
//if not found as registered bean let's try the handler
if(webAppContext==null){
webAppContext = (WebAppContext) server.getHandler();
}
ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
securityHandler.setConstraintMappings(jettyConstraintMappings);
@@ -267,10 +235,12 @@ public class KeycloakAutoConfiguration {
Set<String> authRoles = new HashSet<String>();
for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) {
for (String authRole : constraint.getAuthRoles()) {
if (!authRoles.contains(authRole)) {
context.addSecurityRole(authRole);
authRoles.add(authRole);
for (KeycloakSpringBootProperties.SecurityCollection collection : constraint.getSecurityCollections()) {
for (String authRole : collection.getAuthRoles()) {
if (!authRoles.contains(authRole)) {
context.addSecurityRole(authRole);
authRoles.add(authRole);
}
}
}
}
@@ -278,10 +248,6 @@ public class KeycloakAutoConfiguration {
for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) {
SecurityConstraint tomcatConstraint = new SecurityConstraint();
for (String authRole : constraint.getAuthRoles()) {
tomcatConstraint.addAuthRole(authRole);
}
for (KeycloakSpringBootProperties.SecurityCollection collection : constraint.getSecurityCollections()) {
SecurityCollection tomcatSecCollection = new SecurityCollection();
@@ -292,6 +258,10 @@ public class KeycloakAutoConfiguration {
tomcatSecCollection.setDescription(collection.getDescription());
}
for (String authRole : collection.getAuthRoles()) {
tomcatConstraint.addAuthRole(authRole);
}
for (String pattern : collection.getPatterns()) {
tomcatSecCollection.addPattern(pattern);
}
@@ -34,11 +34,6 @@ public class KeycloakSpringBootProperties extends AdapterConfig {
@JsonIgnore
private Map config = new HashMap();
/**
* Allow enabling of Keycloak Spring Boot adapter by configuration.
*/
private boolean enabled = true;
public Map getConfig() {
return config;
}
@@ -48,28 +43,12 @@ public class KeycloakSpringBootProperties extends AdapterConfig {
*/
private List<SecurityConstraint> securityConstraints = new ArrayList<SecurityConstraint>();
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/**
* This matches security-constraint of the servlet spec
*/
@ConfigurationProperties()
public static class SecurityConstraint {
/**
* A list of security collections
*/
private List<SecurityCollection> securityCollections = new ArrayList<SecurityCollection>();
private List<String> authRoles = new ArrayList<String>();
public List<String> getAuthRoles() {
return authRoles;
}
public List<SecurityCollection> getSecurityCollections() {
return securityCollections;
@@ -78,16 +57,7 @@ public class KeycloakSpringBootProperties extends AdapterConfig {
public void setSecurityCollections(List<SecurityCollection> securityCollections) {
this.securityCollections = securityCollections;
}
public void setAuthRoles(List<String> authRoles) {
this.authRoles = authRoles;
}
}
/**
* This matches web-resource-collection of the servlet spec
*/
@ConfigurationProperties()
public static class SecurityCollection {
/**
@@ -98,6 +68,10 @@ public class KeycloakSpringBootProperties extends AdapterConfig {
* The description of your security collection
*/
private String description;
/**
* A list of roles that applies for this security collection
*/
private List<String> authRoles = new ArrayList<String>();
/**
* A list of URL patterns that should match to apply the security collection
*/
@@ -111,6 +85,10 @@ public class KeycloakSpringBootProperties extends AdapterConfig {
*/
private List<String> omittedMethods = new ArrayList<String>();
public List<String> getAuthRoles() {
return authRoles;
}
public List<String> getPatterns() {
return patterns;
}
@@ -139,6 +117,10 @@ public class KeycloakSpringBootProperties extends AdapterConfig {
this.description = description;
}
public void setAuthRoles(List<String> authRoles) {
this.authRoles = authRoles;
}
public void setPatterns(List<String> patterns) {
this.patterns = patterns;
}
@@ -1,2 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.keycloak.adapters.springboot.KeycloakAutoConfiguration
org.keycloak.adapters.springboot.KeycloakSpringBootConfiguration
+1 -3
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -59,14 +59,12 @@
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring-security.version}</version>
<optional>true</optional>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${spring-security.version}</version>
<optional>true</optional>
<scope>compile</scope>
</dependency>
<dependency>
@@ -1,25 +0,0 @@
package org.keycloak.adapters.springsecurity;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Add this annotation to a class that extends {@code KeycloakWebSecurityConfigurerAdapter} to provide
* a keycloak based Spring security configuration.
*
* @author Hendrik Ebbers
*/
@Retention(value = RUNTIME)
@Target(value = { TYPE })
@Configuration
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
@EnableWebSecurity
public @interface KeycloakConfiguration {
}
@@ -1,41 +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.
*/
package org.keycloak.adapters.springsecurity.authentication;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
/**
* To return the forbidden code with the corresponding message.
*
* @author emilienbondu
*
*/
public class KeycloakAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unable to authenticate using the Authorization header");
}
}
@@ -17,13 +17,6 @@
package org.keycloak.adapters.springsecurity.filter;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment;
@@ -32,7 +25,7 @@ import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.springsecurity.KeycloakAuthenticationException;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationFailureHandler;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationEntryPoint;
import org.keycloak.adapters.springsecurity.authentication.SpringSecurityRequestAuthenticator;
import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
import org.keycloak.adapters.springsecurity.token.AdapterTokenStoreFactory;
@@ -48,12 +41,19 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Provides a Keycloak authentication processing filter.
*
@@ -61,13 +61,11 @@ import org.springframework.util.Assert;
* @version $Revision: 1 $
*/
public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter implements ApplicationContextAware {
public static final String DEFAULT_LOGIN_URL = "/sso/login";
public static final String AUTHORIZATION_HEADER = "Authorization";
public static final String SCHEME_BEARER = "bearer ";
public static final String SCHEME_BASIC = "basic ";
/**
* Request matcher that matches requests to the {@link KeycloakAuthenticationEntryPoint#DEFAULT_LOGIN_URI default login URI}
* and any request with a <code>Authorization</code> header.
@@ -91,7 +89,7 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
*/
public KeycloakAuthenticationProcessingFilter(AuthenticationManager authenticationManager) {
this(authenticationManager, DEFAULT_REQUEST_MATCHER);
setAuthenticationFailureHandler(new KeycloakAuthenticationFailureHandler());
setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(DEFAULT_LOGIN_URL));
}
/**
@@ -111,7 +109,7 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
*
*/
public KeycloakAuthenticationProcessingFilter(AuthenticationManager authenticationManager, RequestMatcher
requiresAuthenticationRequestMatcher) {
requiresAuthenticationRequestMatcher) {
super(requiresAuthenticationRequestMatcher);
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
this.authenticationManager = authenticationManager;
@@ -142,27 +140,9 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
log.debug("Auth outcome: {}", result);
if (AuthOutcome.FAILED.equals(result)) {
AuthChallenge challenge = authenticator.getChallenge();
if (challenge != null) {
challenge.challenge(facade);
}
throw new KeycloakAuthenticationException("Invalid authorization header, see WWW-Authenticate header for details");
throw new KeycloakAuthenticationException("Auth outcome: " + result);
}
if (AuthOutcome.NOT_ATTEMPTED.equals(result)) {
AuthChallenge challenge = authenticator.getChallenge();
if (challenge != null) {
challenge.challenge(facade);
}
if (deployment.isBearerOnly()) {
// no redirection in this mode, throwing exception for the spring handler
throw new KeycloakAuthenticationException("Authorization header not found, see WWW-Authenticate header");
} else {
// let continue if challenged, it may redirect
return null;
}
}
else if (AuthOutcome.AUTHENTICATED.equals(result)) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Assert.notNull(authentication, "Authentication SecurityContextHolder was null");
@@ -204,7 +184,7 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
Authentication authResult) throws IOException, ServletException {
if (!(this.isBearerTokenRequest(request) || this.isBasicAuthRequest(request))) {
super.successfulAuthentication(request, response, chain, authResult);
@@ -232,7 +212,19 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
AuthenticationException failed) throws IOException, ServletException {
if (this.isBearerTokenRequest(request)) {
SecurityContextHolder.clearContext();
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unable to authenticate bearer token");
return;
}
else if (this.isBasicAuthRequest(request)) {
SecurityContextHolder.clearContext();
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unable to authenticate with basic authentication");
return;
}
super.unsuccessfulAuthentication(request, response, failed);
}
@@ -270,4 +262,4 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
public final void setContinueChainBeforeSuccessfulAuthentication(boolean continueChainBeforeSuccessfulAuthentication) {
throw new UnsupportedOperationException("This filter does not support explicitly setting a continue chain before success policy");
}
}
}
@@ -27,7 +27,6 @@ import org.keycloak.adapters.OidcKeycloakAccount;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.springsecurity.KeycloakAuthenticationException;
import org.keycloak.adapters.springsecurity.account.KeycloakRole;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationFailureHandler;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.common.util.KeycloakUriBuilder;
@@ -90,8 +89,6 @@ public class KeycloakAuthenticationProcessingFilterTest {
@Mock
private AuthenticationFailureHandler failureHandler;
private KeycloakAuthenticationFailureHandler keycloakFailureHandler;
@Mock
private OidcKeycloakAccount keycloakAccount;
@@ -109,7 +106,6 @@ public class KeycloakAuthenticationProcessingFilterTest {
MockitoAnnotations.initMocks(this);
request = spy(new MockHttpServletRequest());
filter = new KeycloakAuthenticationProcessingFilter(authenticationManager);
keycloakFailureHandler = new KeycloakAuthenticationFailureHandler();
filter.setApplicationContext(applicationContext);
filter.setAuthenticationSuccessHandler(successHandler);
@@ -159,8 +155,8 @@ public class KeycloakAuthenticationProcessingFilterTest {
when(keycloakDeployment.getStateCookieName()).thenReturn("kc-cookie");
when(keycloakDeployment.getSslRequired()).thenReturn(SslRequired.NONE);
when(keycloakDeployment.isBearerOnly()).thenReturn(Boolean.FALSE);
filter.attemptAuthentication(request, response);
verify(response).setStatus(302);
verify(response).setHeader(eq("Location"), startsWith("http://localhost:8080/auth"));
}
@@ -171,13 +167,6 @@ public class KeycloakAuthenticationProcessingFilterTest {
filter.attemptAuthentication(request, response);
}
@Test(expected = KeycloakAuthenticationException.class)
public void testAttemptAuthenticationWithInvalidTokenBearerOnly() throws Exception {
when(keycloakDeployment.isBearerOnly()).thenReturn(Boolean.TRUE);
request.addHeader("Authorization", "Bearer xxx");
filter.attemptAuthentication(request, response);
}
@Test
public void testSuccessfulAuthenticationInteractive() throws Exception {
Authentication authentication = new KeycloakAuthenticationToken(keycloakAccount, authorities);
@@ -221,7 +210,8 @@ public class KeycloakAuthenticationProcessingFilterTest {
AuthenticationException exception = new BadCredentialsException("OOPS");
this.setBearerAuthHeader(request);
filter.unsuccessfulAuthentication(request, response, exception);
verify(failureHandler).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
verify(response).sendError(eq(HttpServletResponse.SC_UNAUTHORIZED), anyString());
verify(failureHandler, never()).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
any(AuthenticationException.class));
}
@@ -230,18 +220,10 @@ public class KeycloakAuthenticationProcessingFilterTest {
AuthenticationException exception = new BadCredentialsException("OOPS");
this.setBasicAuthHeader(request);
filter.unsuccessfulAuthentication(request, response, exception);
verify(failureHandler).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
verify(response).sendError(eq(HttpServletResponse.SC_UNAUTHORIZED), anyString());
verify(failureHandler, never()).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
any(AuthenticationException.class));
}
@Test
public void testDefaultFailureHanlder() throws Exception {
AuthenticationException exception = new BadCredentialsException("OOPS");
filter.setAuthenticationFailureHandler(keycloakFailureHandler);
filter.unsuccessfulAuthentication(request, response, exception);
verify(response).sendError(eq(HttpServletResponse.SC_UNAUTHORIZED), any(String.class));
}
@Test(expected = UnsupportedOperationException.class)
public void testSetAllowSessionCreation() throws Exception {
+3 -16
View File
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak Tomcat Integration</name>
@@ -32,21 +32,8 @@
<modules>
<module>tomcat-core</module>
<module>tomcat6</module>
<module>tomcat7</module>
<module>tomcat8</module>
</modules>
<profiles>
<profile>
<id>community</id>
<activation>
<property>
<name>!product</name>
</property>
</activation>
<modules>
<module>tomcat6</module>
<module>tomcat7</module>
</modules>
</profile>
</profiles>
</project>
+3 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -29,6 +29,8 @@
<artifactId>keycloak-tomcat-core-adapter</artifactId>
<name>Keycloak Tomcat Core Integration</name>
<properties>
<!-- <tomcat.version>8.0.14</tomcat.version> -->
<!-- <tomcat.version>7.0.52</tomcat.version> -->
<tomcat.version>6.0.41</tomcat.version>
</properties>
<description />
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
-99
View File
@@ -1,99 +0,0 @@
<?xml version="1.0"?>
<!--
~ JBoss, Home of Professional Open Source.
~ Copyright 2016 Red Hat, Inc., and individual 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">
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-wildfly-elytron-oidc-adapter</artifactId>
<name>Keycloak Wildfly Elytron OIDC Adapter</name>
<description/>
<properties>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>
<dependencies>
<dependency>
<groupId>org.wildfly.common</groupId>
<artifactId>wildfly-common</artifactId>
<version>1.2.0.Beta1</version>
</dependency>
<dependency>
<groupId>org.wildfly.security</groupId>
<artifactId>wildfly-elytron</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.wildfly.security.elytron-web</groupId>
<artifactId>undertow-server</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>${jboss.logging.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-spi</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
@@ -1,103 +0,0 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2016 Red Hat, Inc., and individual 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.elytron;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OidcKeycloakAccount;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.wildfly.security.auth.server.SecurityIdentity;
import javax.security.auth.callback.CallbackHandler;
import java.security.Principal;
import java.util.HashSet;
import java.util.Set;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class ElytronAccount implements OidcKeycloakAccount {
protected static Logger log = Logger.getLogger(ElytronAccount.class);
private final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal;
public ElytronAccount(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
this.principal = principal;
}
@Override
public RefreshableKeycloakSecurityContext getKeycloakSecurityContext() {
return principal.getKeycloakSecurityContext();
}
@Override
public Principal getPrincipal() {
return principal;
}
@Override
public Set<String> getRoles() {
Set<String> roles = new HashSet<>();
return roles;
}
void setCurrentRequestInfo(KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
principal.getKeycloakSecurityContext().setCurrentRequestInfo(deployment, tokenStore);
}
public boolean checkActive() {
RefreshableKeycloakSecurityContext session = getKeycloakSecurityContext();
if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) {
log.debug("session is active");
return true;
}
log.debug("session not active");
return false;
}
boolean tryRefresh(CallbackHandler callbackHandler) {
log.debug("Trying to refresh");
RefreshableKeycloakSecurityContext securityContext = getKeycloakSecurityContext();
if (securityContext == null) {
log.debug("No security context. Aborting refresh.");
}
if (securityContext.refreshExpiredToken(false)) {
SecurityIdentity securityIdentity = SecurityIdentityUtil.authorize(callbackHandler, principal);
if (securityIdentity != null) {
log.debug("refresh succeeded");
return true;
}
return false;
}
return checkActive();
}
}
@@ -1,164 +0,0 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2016 Red Hat, Inc., and individual 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.elytron;
import java.security.Principal;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.CookieTokenStore;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OidcKeycloakAccount;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.RequestAuthenticator;
import org.wildfly.security.http.HttpScope;
import org.wildfly.security.http.Scope;
import javax.security.auth.callback.CallbackHandler;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class ElytronCookieTokenStore implements ElytronTokeStore {
protected static Logger log = Logger.getLogger(ElytronCookieTokenStore.class);
private final ElytronHttpFacade httpFacade;
private final CallbackHandler callbackHandler;
public ElytronCookieTokenStore(ElytronHttpFacade httpFacade, CallbackHandler callbackHandler) {
this.httpFacade = httpFacade;
this.callbackHandler = callbackHandler;
}
@Override
public void checkCurrentToken() {
KeycloakDeployment deployment = httpFacade.getDeployment();
KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = CookieTokenStore.getPrincipalFromCookie(deployment, httpFacade, this);
if (principal == null) {
return;
}
RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
if (securityContext.isActive() && !securityContext.getDeployment().isAlwaysRefreshToken()) return;
// FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will
// not be updated
boolean success = securityContext.refreshExpiredToken(false);
if (success && securityContext.isActive()) return;
saveAccountInfo(new ElytronAccount(principal));
}
@Override
public boolean isCached(RequestAuthenticator authenticator) {
KeycloakDeployment deployment = httpFacade.getDeployment();
KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = CookieTokenStore.getPrincipalFromCookie(deployment, httpFacade, this);
if (principal == null) {
log.debug("Account was not in cookie or was invalid, returning null");
return false;
}
ElytronAccount account = new ElytronAccount(principal);
if (!deployment.getRealm().equals(account.getKeycloakSecurityContext().getRealm())) {
log.debug("Account in session belongs to a different realm than for this request.");
return false;
}
boolean active = account.checkActive();
if (!active) {
active = account.tryRefresh(this.callbackHandler);
}
if (active) {
log.debug("Cached account found");
restoreRequest();
httpFacade.authenticationComplete(account, true);
return true;
} else {
log.debug("Account was not active, removing cookie and returning false");
CookieTokenStore.removeCookie(httpFacade);
return false;
}
}
@Override
public void saveAccountInfo(OidcKeycloakAccount account) {
RefreshableKeycloakSecurityContext secContext = (RefreshableKeycloakSecurityContext)account.getKeycloakSecurityContext();
CookieTokenStore.setTokenCookie(this.httpFacade.getDeployment(), this.httpFacade, secContext);
HttpScope exchange = this.httpFacade.getScope(Scope.EXCHANGE);
exchange.registerForNotification(httpServerScopes -> logout());
exchange.setAttachment(ElytronAccount.class.getName(), account);
exchange.setAttachment(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext());
restoreRequest();
}
@Override
public void logout() {
logout(false);
}
@Override
public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
CookieTokenStore.setTokenCookie(this.httpFacade.getDeployment(), httpFacade, securityContext);
}
@Override
public void saveRequest() {
}
@Override
public boolean restoreRequest() {
return false;
}
@Override
public void logout(boolean glo) {
KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = CookieTokenStore.getPrincipalFromCookie(this.httpFacade.getDeployment(), this.httpFacade, this);
if (principal == null) {
return;
}
CookieTokenStore.removeCookie(this.httpFacade);
if (glo) {
KeycloakSecurityContext ksc = (KeycloakSecurityContext) principal.getKeycloakSecurityContext();
if (ksc == null) {
return;
}
KeycloakDeployment deployment = httpFacade.getDeployment();
if (!deployment.isBearerOnly() && ksc != null && ksc instanceof RefreshableKeycloakSecurityContext) {
((RefreshableKeycloakSecurityContext) ksc).logout(deployment);
}
}
}
}
@@ -1,394 +0,0 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2016 Red Hat, Inc., and individual 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.elytron;
import org.bouncycastle.asn1.cmp.Challenge;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthenticationError;
import org.keycloak.adapters.spi.LogoutError;
import org.keycloak.enums.TokenStore;
import org.wildfly.security.auth.server.SecurityIdentity;
import org.wildfly.security.http.HttpAuthenticationException;
import org.wildfly.security.http.HttpScope;
import org.wildfly.security.http.HttpServerCookie;
import org.wildfly.security.http.HttpServerMechanismsResponder;
import org.wildfly.security.http.HttpServerRequest;
import org.wildfly.security.http.HttpServerResponse;
import org.wildfly.security.http.Scope;
import javax.security.auth.callback.CallbackHandler;
import javax.security.cert.X509Certificate;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URLDecoder;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
class ElytronHttpFacade implements OIDCHttpFacade {
private final HttpServerRequest request;
private final CallbackHandler callbackHandler;
private final AdapterTokenStore tokenStore;
private final AdapterDeploymentContext deploymentContext;
private Consumer<HttpServerResponse> responseConsumer;
private ElytronAccount account;
private SecurityIdentity securityIdentity;
private boolean restored;
public ElytronHttpFacade(HttpServerRequest request, AdapterDeploymentContext deploymentContext, CallbackHandler handler) {
this.request = request;
this.deploymentContext = deploymentContext;
this.callbackHandler = handler;
this.tokenStore = createTokenStore();
this.responseConsumer = response -> {};
}
void authenticationComplete(ElytronAccount account, boolean storeToken) {
this.securityIdentity = SecurityIdentityUtil.authorize(this.callbackHandler, account.getPrincipal());
if (securityIdentity != null) {
this.account = account;
RefreshableKeycloakSecurityContext keycloakSecurityContext = account.getKeycloakSecurityContext();
account.setCurrentRequestInfo(keycloakSecurityContext.getDeployment(), this.tokenStore);
if (storeToken) {
this.tokenStore.saveAccountInfo(account);
}
}
}
void authenticationComplete() {
if (securityIdentity != null) {
this.request.authenticationComplete(response -> {
if (!restored) {
responseConsumer.accept(response);
}
}, () -> ((ElytronTokeStore) tokenStore).logout(true));
}
}
void authenticationFailed() {
this.request.authenticationFailed("Authentication Failed", response -> responseConsumer.accept(response));
}
void noAuthenticationInProgress() {
this.request.noAuthenticationInProgress();
}
void noAuthenticationInProgress(AuthChallenge challenge) {
if (challenge != null) {
challenge.challenge(this);
}
this.request.noAuthenticationInProgress(response -> responseConsumer.accept(response));
}
void authenticationInProgress() {
this.request.authenticationInProgress(response -> responseConsumer.accept(response));
}
HttpScope getScope(Scope scope) {
return request.getScope(scope);
}
HttpScope getScope(Scope scope, String id) {
return request.getScope(scope, id);
}
Collection<String> getScopeIds(Scope scope) {
return request.getScopeIds(scope);
}
AdapterTokenStore getTokenStore() {
return this.tokenStore;
}
KeycloakDeployment getDeployment() {
return deploymentContext.resolveDeployment(this);
}
private AdapterTokenStore createTokenStore() {
KeycloakDeployment deployment = getDeployment();
if (TokenStore.SESSION.equals(deployment.getTokenStore())) {
return new ElytronSessionTokenStore(this, this.callbackHandler);
} else {
return new ElytronCookieTokenStore(this, this.callbackHandler);
}
}
@Override
public Request getRequest() {
return new Request() {
@Override
public String getMethod() {
return request.getRequestMethod();
}
@Override
public String getURI() {
try {
return URLDecoder.decode(request.getRequestURI().toString(), "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Failed to decode request URI", e);
}
}
@Override
public String getRelativePath() {
return request.getRequestPath();
}
@Override
public boolean isSecure() {
return request.getRequestURI().getScheme().equals("https");
}
@Override
public String getFirstParam(String param) {
throw new RuntimeException("Not implemented.");
}
@Override
public String getQueryParamValue(String param) {
URI requestURI = request.getRequestURI();
String query = requestURI.getQuery();
if (query != null) {
String[] parameters = query.split("&");
for (String parameter : parameters) {
String[] keyValue = parameter.split("=");
if (keyValue[0].equals(param)) {
return keyValue[1];
}
}
}
return null;
}
@Override
public Cookie getCookie(final String cookieName) {
List<HttpServerCookie> cookies = request.getCookies();
if (cookies != null) {
for (HttpServerCookie cookie : cookies) {
if (cookie.getName().equals(cookieName)) {
return new Cookie(cookie.getName(), cookie.getValue(), cookie.getVersion(), cookie.getDomain(), cookie.getPath());
}
}
}
return null;
}
@Override
public String getHeader(String name) {
return request.getFirstRequestHeaderValue(name);
}
@Override
public List<String> getHeaders(String name) {
return request.getRequestHeaderValues(name);
}
@Override
public InputStream getInputStream() {
return request.getInputStream();
}
@Override
public String getRemoteAddr() {
InetSocketAddress sourceAddress = request.getSourceAddress();
if (sourceAddress == null) {
return "";
}
InetAddress address = sourceAddress.getAddress();
if (address == null) {
// this is unresolved, so we just return the host name not exactly spec, but if the name should be
// resolved then a PeerNameResolvingHandler should be used and this is probably better than just
// returning null
return sourceAddress.getHostString();
}
return address.getHostAddress();
}
@Override
public void setError(AuthenticationError error) {
request.getScope(Scope.EXCHANGE).setAttachment(AuthenticationError.class.getName(), error);
}
@Override
public void setError(LogoutError error) {
request.getScope(Scope.EXCHANGE).setAttachment(LogoutError.class.getName(), error);
}
};
}
@Override
public Response getResponse() {
return new Response() {
@Override
public void setStatus(final int status) {
responseConsumer = responseConsumer.andThen(response -> response.setStatusCode(status));
}
@Override
public void addHeader(final String name, final String value) {
responseConsumer = responseConsumer.andThen(response -> response.addResponseHeader(name, value));
}
@Override
public void setHeader(String name, String value) {
addHeader(name, value);
}
@Override
public void resetCookie(final String name, final String path) {
responseConsumer = responseConsumer.andThen(response -> setCookie(name, "", path, null, 0, false, false, response));
}
@Override
public void setCookie(final String name, final String value, final String path, final String domain, final int maxAge, final boolean secure, final boolean httpOnly) {
responseConsumer = responseConsumer.andThen(response -> setCookie(name, value, path, domain, maxAge, secure, httpOnly, response));
}
private void setCookie(final String name, final String value, final String path, final String domain, final int maxAge, final boolean secure, final boolean httpOnly, HttpServerResponse response) {
response.setResponseCookie(new HttpServerCookie() {
@Override
public String getName() {
return name;
}
@Override
public String getValue() {
return value;
}
@Override
public String getDomain() {
return domain;
}
@Override
public int getMaxAge() {
return maxAge;
}
@Override
public String getPath() {
return path;
}
@Override
public boolean isSecure() {
return secure;
}
@Override
public int getVersion() {
return 0;
}
@Override
public boolean isHttpOnly() {
return httpOnly;
}
});
}
@Override
public OutputStream getOutputStream() {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
responseConsumer = responseConsumer.andThen(new Consumer<HttpServerResponse>() {
@Override
public void accept(HttpServerResponse httpServerResponse) {
try {
httpServerResponse.getOutputStream().write(stream.toByteArray());
} catch (IOException e) {
throw new RuntimeException("Failed to write to response output stream", e);
}
}
});
return stream;
}
@Override
public void sendError(int code) {
setStatus(code);
}
@Override
public void sendError(final int code, final String message) {
responseConsumer = responseConsumer.andThen(response -> {
response.setStatusCode(code);
response.addResponseHeader("Content-Type", "text/html");
try {
response.getOutputStream().write(message.getBytes());
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
@Override
public void end() {
}
};
}
@Override
public X509Certificate[] getCertificateChain() {
return new X509Certificate[0];
}
@Override
public KeycloakSecurityContext getSecurityContext() {
if (account == null) {
return null;
}
return this.account.getKeycloakSecurityContext();
}
public boolean restoreRequest() {
restored = this.request.resumeRequest();
return restored;
}
public void suspendRequest() {
responseConsumer = responseConsumer.andThen(httpServerResponse -> request.suspendRequest());
}
public boolean isAuthorized() {
return this.securityIdentity != null;
}
}
@@ -1,86 +0,0 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2016 Red Hat, Inc., and individual 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.elytron;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.adapters.BearerTokenRequestAuthenticator;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OAuthRequestAuthenticator;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.RequestAuthenticator;
import org.keycloak.adapters.spi.AuthOutcome;
import org.wildfly.security.http.HttpScope;
import org.wildfly.security.http.Scope;
import javax.security.auth.callback.CallbackHandler;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class ElytronRequestAuthenticator extends RequestAuthenticator {
public ElytronRequestAuthenticator(CallbackHandler callbackHandler, ElytronHttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort) {
super(facade, deployment, facade.getTokenStore(), sslRedirectPort);
}
@Override
public AuthOutcome authenticate() {
AuthOutcome authenticate = super.authenticate();
if (AuthOutcome.AUTHENTICATED.equals(authenticate)) {
if (!getElytronHttpFacade().isAuthorized()) {
return AuthOutcome.FAILED;
}
}
return authenticate;
}
@Override
protected OAuthRequestAuthenticator createOAuthAuthenticator() {
return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort, tokenStore);
}
@Override
protected void completeOAuthAuthentication(final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
getElytronHttpFacade().authenticationComplete(new ElytronAccount(principal), true);
}
@Override
protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method) {
getElytronHttpFacade().authenticationComplete(new ElytronAccount(principal), false);
}
@Override
protected String changeHttpSessionId(boolean create) {
HttpScope session = getElytronHttpFacade().getScope(Scope.SESSION);
if (create) {
if (!session.exists()) {
session.create();
}
}
return session != null ? session.getID() : null;
}
private ElytronHttpFacade getElytronHttpFacade() {
return (ElytronHttpFacade) facade;
}
}
@@ -1,202 +0,0 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2016 Red Hat, Inc., and individual 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.elytron;
import java.util.function.Consumer;
import javax.security.auth.callback.CallbackHandler;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.AdapterUtils;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OidcKeycloakAccount;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.RequestAuthenticator;
import org.wildfly.security.http.HttpScope;
import org.wildfly.security.http.HttpScopeNotification;
import org.wildfly.security.http.Scope;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class ElytronSessionTokenStore implements ElytronTokeStore {
private static Logger log = Logger.getLogger(ElytronSessionTokenStore.class);
private final ElytronHttpFacade httpFacade;
private final CallbackHandler callbackHandler;
public ElytronSessionTokenStore(ElytronHttpFacade httpFacade, CallbackHandler callbackHandler) {
this.httpFacade = httpFacade;
this.callbackHandler = callbackHandler;
}
@Override
public void checkCurrentToken() {
HttpScope session = httpFacade.getScope(Scope.SESSION);
if (!session.exists()) return;
RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) session.getAttachment(KeycloakSecurityContext.class.getName());
if (securityContext == null) return;
// just in case session got serialized
if (securityContext.getDeployment() == null) securityContext.setCurrentRequestInfo(httpFacade.getDeployment(), this);
if (securityContext.isActive() && !securityContext.getDeployment().isAlwaysRefreshToken()) return;
// FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will
// not be updated
boolean success = securityContext.refreshExpiredToken(false);
if (success && securityContext.isActive()) return;
// Refresh failed, so user is already logged out from keycloak. Cleanup and expire our session
session.setAttachment(KeycloakSecurityContext.class.getName(), null);
session.invalidate();
}
@Override
public boolean isCached(RequestAuthenticator authenticator) {
HttpScope session = this.httpFacade.getScope(Scope.SESSION);
if (session == null || !session.supportsAttachments()) {
log.debug("session was null, returning null");
return false;
}
ElytronAccount account;
try {
account = (ElytronAccount) session.getAttachment(ElytronAccount.class.getName());
} catch (IllegalStateException e) {
log.debug("session was invalidated. Return false.");
return false;
}
if (account == null) {
log.debug("Account was not in session, returning null");
return false;
}
KeycloakDeployment deployment = httpFacade.getDeployment();
if (!deployment.getRealm().equals(account.getKeycloakSecurityContext().getRealm())) {
log.debug("Account in session belongs to a different realm than for this request.");
return false;
}
boolean active = account.checkActive();
if (!active) {
active = account.tryRefresh(this.callbackHandler);
}
if (active) {
log.debug("Cached account found");
restoreRequest();
httpFacade.authenticationComplete(account, true);
return true;
} else {
log.debug("Refresh failed. Account was not active. Returning null and invalidating Http session");
try {
session.setAttachment(KeycloakSecurityContext.class.getName(), null);
session.setAttachment(ElytronAccount.class.getName(), null);
session.invalidate();
} catch (Exception e) {
log.debug("Failed to invalidate session, might already be invalidated");
}
return false;
}
}
@Override
public void saveAccountInfo(OidcKeycloakAccount account) {
HttpScope session = this.httpFacade.getScope(Scope.SESSION);
if (!session.exists()) {
session.create();
}
session.setAttachment(ElytronAccount.class.getName(), account);
session.setAttachment(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext());
session.registerForNotification(httpScopeNotification -> {
if (!httpScopeNotification.isOfType(HttpScopeNotification.SessionNotificationType.UNDEPLOY)) {
logout();
}
});
HttpScope scope = this.httpFacade.getScope(Scope.EXCHANGE);
scope.setAttachment(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext());
}
@Override
public void logout() {
logout(false);
}
@Override
public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(AdapterUtils.getPrincipalName(this.httpFacade.getDeployment(), securityContext.getToken()), securityContext);
saveAccountInfo(new ElytronAccount(principal));
}
@Override
public void saveRequest() {
this.httpFacade.suspendRequest();
}
@Override
public boolean restoreRequest() {
return this.httpFacade.restoreRequest();
}
@Override
public void logout(boolean glo) {
HttpScope session = this.httpFacade.getScope(Scope.SESSION);
if (!session.exists()) {
return;
}
try {
if (glo) {
KeycloakSecurityContext ksc = (KeycloakSecurityContext) session.getAttachment(KeycloakSecurityContext.class.getName());
if (ksc == null) {
return;
}
KeycloakDeployment deployment = httpFacade.getDeployment();
if (!deployment.isBearerOnly() && ksc != null && ksc instanceof RefreshableKeycloakSecurityContext) {
((RefreshableKeycloakSecurityContext) ksc).logout(deployment);
}
}
session.setAttachment(KeycloakSecurityContext.class.getName(), null);
session.setAttachment(ElytronAccount.class.getName(), null);
session.invalidate();
} catch (IllegalStateException ise) {
// Session may be already logged-out in case that app has adminUrl
log.debugf("Session %s logged-out already", session.getID());
}
}
}
@@ -1,26 +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.
*/
package org.keycloak.adapters.elytron;
import org.keycloak.adapters.AdapterTokenStore;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public interface ElytronTokeStore extends AdapterTokenStore {
void logout(boolean glo);
}
@@ -1,109 +0,0 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2016 Red Hat, Inc., and individual 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.elytron;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.constants.AdapterConstants;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
/**
* <p>A {@link ServletContextListener} that parses the keycloak adapter configuration and set the same configuration
* as a {@link ServletContext} attribute in order to provide to {@link KeycloakHttpServerAuthenticationMechanism} a way
* to obtain the configuration when processing requests.
*
* <p>This listener should be automatically registered to a deployment using the subsystem.
*
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class KeycloakConfigurationServletListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext servletContext = sce.getServletContext();
String configResolverClass = servletContext.getInitParameter("keycloak.config.resolver");
KeycloakConfigResolver configResolver;
AdapterDeploymentContext deploymentContext;
if (configResolverClass != null) {
try {
configResolver = (KeycloakConfigResolver) servletContext.getClassLoader().loadClass(configResolverClass).newInstance();
deploymentContext = new AdapterDeploymentContext(configResolver);
} catch (Exception ex) {
deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
}
} else {
InputStream is = getConfigInputStream(servletContext);
KeycloakDeployment deployment;
if (is == null) {
deployment = new KeycloakDeployment();
} else {
deployment = KeycloakDeploymentBuilder.build(is);
}
deploymentContext = new AdapterDeploymentContext(deployment);
}
servletContext.setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
private InputStream getConfigInputStream(ServletContext servletContext) {
InputStream is = getJSONFromServletContext(servletContext);
if (is == null) {
String path = servletContext.getInitParameter("keycloak.config.file");
if (path == null) {
is = servletContext.getResourceAsStream("/WEB-INF/keycloak.json");
} else {
try {
is = new FileInputStream(path);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
}
return is;
}
private InputStream getJSONFromServletContext(ServletContext servletContext) {
String json = servletContext.getInitParameter(AdapterConstants.AUTH_DATA_PARAM_NAME);
if (json == null) {
return null;
}
return new ByteArrayInputStream(json.getBytes());
}
}
@@ -1,168 +0,0 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2016 Red Hat, Inc., and individual 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.elytron;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.security.auth.callback.CallbackHandler;
import org.jboss.logging.Logger;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.AuthenticatedActionsHandler;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.NodesRegistrationManagement;
import org.keycloak.adapters.PreAuthActionsHandler;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.RequestAuthenticator;
import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.UserSessionManagement;
import org.wildfly.security.http.HttpAuthenticationException;
import org.wildfly.security.http.HttpScope;
import org.wildfly.security.http.HttpServerAuthenticationMechanism;
import org.wildfly.security.http.HttpServerRequest;
import org.wildfly.security.http.Scope;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
class KeycloakHttpServerAuthenticationMechanism implements HttpServerAuthenticationMechanism {
static Logger LOGGER = Logger.getLogger(KeycloakHttpServerAuthenticationMechanismFactory.class);
static final String NAME = "KEYCLOAK";
private final Map<String, ?> properties;
private final CallbackHandler callbackHandler;
private final AdapterDeploymentContext deploymentContext;
public KeycloakHttpServerAuthenticationMechanism(Map<String, ?> properties, CallbackHandler callbackHandler, AdapterDeploymentContext deploymentContext) {
this.properties = properties;
this.callbackHandler = callbackHandler;
this.deploymentContext = deploymentContext;
}
@Override
public String getMechanismName() {
return NAME;
}
@Override
public void evaluateRequest(HttpServerRequest request) throws HttpAuthenticationException {
LOGGER.debugf("Evaluating request for path [%s]", request.getRequestURI());
AdapterDeploymentContext deploymentContext = getDeploymentContext(request);
if (deploymentContext == null) {
LOGGER.debugf("Ignoring request for path [%s] from mechanism [%s]. No deployment context found.", request.getRequestURI());
request.noAuthenticationInProgress();
return;
}
ElytronHttpFacade httpFacade = new ElytronHttpFacade(request, deploymentContext, callbackHandler);
KeycloakDeployment deployment = httpFacade.getDeployment();
if (!deployment.isConfigured()) {
request.noAuthenticationInProgress();
return;
}
RequestAuthenticator authenticator = createRequestAuthenticator(request, httpFacade, deployment);
httpFacade.getTokenStore().checkCurrentToken();
if (preActions(httpFacade, deploymentContext)) {
LOGGER.debugf("Pre-actions has aborted the evaluation of [%s]", request.getRequestURI());
httpFacade.authenticationInProgress();
return;
}
AuthOutcome outcome = authenticator.authenticate();
if (AuthOutcome.AUTHENTICATED.equals(outcome)) {
if (new AuthenticatedActionsHandler(deployment, httpFacade).handledRequest()) {
httpFacade.authenticationInProgress();
} else {
httpFacade.authenticationComplete();
}
return;
}
AuthChallenge challenge = authenticator.getChallenge();
if (challenge != null) {
httpFacade.noAuthenticationInProgress(challenge);
return;
}
if (AuthOutcome.FAILED.equals(outcome)) {
httpFacade.getResponse().setStatus(403);
httpFacade.authenticationFailed();
return;
}
httpFacade.noAuthenticationInProgress();
}
private ElytronRequestAuthenticator createRequestAuthenticator(HttpServerRequest request, ElytronHttpFacade httpFacade, KeycloakDeployment deployment) {
return new ElytronRequestAuthenticator(this.callbackHandler, httpFacade, deployment, getConfidentialPort(request));
}
private AdapterDeploymentContext getDeploymentContext(HttpServerRequest request) {
if (this.deploymentContext == null) {
return (AdapterDeploymentContext) request.getScope(Scope.APPLICATION).getAttachment(AdapterDeploymentContext.class.getName());
}
return this.deploymentContext;
}
private boolean preActions(ElytronHttpFacade httpFacade, AdapterDeploymentContext deploymentContext) {
NodesRegistrationManagement nodesRegistrationManagement = new NodesRegistrationManagement();
nodesRegistrationManagement.tryRegister(httpFacade.getDeployment());
PreAuthActionsHandler preActions = new PreAuthActionsHandler(new UserSessionManagement() {
@Override
public void logoutAll() {
Collection<String> sessions = httpFacade.getScopeIds(Scope.SESSION);
logoutHttpSessions(new ArrayList<>(sessions));
}
@Override
public void logoutHttpSessions(List<String> ids) {
for (String id : ids) {
HttpScope session = httpFacade.getScope(Scope.SESSION, id);
if (session != null) {
session.invalidate();
}
}
}
}, deploymentContext, httpFacade);
return preActions.handleRequest();
}
// TODO: obtain confidential port from Elytron
private int getConfidentialPort(HttpServerRequest request) {
return 8443;
}
}
@@ -1,67 +0,0 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2016 Red Hat, Inc., and individual 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.elytron;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.wildfly.security.http.HttpAuthenticationException;
import org.wildfly.security.http.HttpServerAuthenticationMechanism;
import org.wildfly.security.http.HttpServerAuthenticationMechanismFactory;
import javax.security.auth.callback.CallbackHandler;
import java.util.HashMap;
import java.util.Map;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class KeycloakHttpServerAuthenticationMechanismFactory implements HttpServerAuthenticationMechanismFactory {
private final AdapterDeploymentContext deploymentContext;
/**
* <p>Creates a new instance.
*
* <p>A default constructor is necessary in order to allow this factory to be loaded via {@link java.util.ServiceLoader}.
*/
public KeycloakHttpServerAuthenticationMechanismFactory() {
this(null);
}
public KeycloakHttpServerAuthenticationMechanismFactory(AdapterDeploymentContext deploymentContext) {
this.deploymentContext = deploymentContext;
}
@Override
public String[] getMechanismNames(Map<String, ?> properties) {
return new String[] {KeycloakHttpServerAuthenticationMechanism.NAME};
}
@Override
public HttpServerAuthenticationMechanism createAuthenticationMechanism(String mechanismName, Map<String, ?> properties, CallbackHandler callbackHandler) throws HttpAuthenticationException {
Map<String, Object> mechanismProperties = new HashMap();
mechanismProperties.putAll(properties);
if (KeycloakHttpServerAuthenticationMechanism.NAME.equals(mechanismName)) {
return new KeycloakHttpServerAuthenticationMechanism(properties, callbackHandler, this.deploymentContext);
}
return null;
}
}
@@ -1,103 +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.
*/
package org.keycloak.adapters.elytron;
import java.security.Principal;
import java.util.Set;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.adapters.AdapterUtils;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.wildfly.security.auth.SupportLevel;
import org.wildfly.security.auth.server.RealmIdentity;
import org.wildfly.security.auth.server.RealmUnavailableException;
import org.wildfly.security.auth.server.SecurityRealm;
import org.wildfly.security.authz.Attributes;
import org.wildfly.security.authz.AuthorizationIdentity;
import org.wildfly.security.authz.MapAttributes;
import org.wildfly.security.authz.RoleDecoder;
import org.wildfly.security.credential.Credential;
import org.wildfly.security.evidence.Evidence;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class KeycloakSecurityRealm implements SecurityRealm {
@Override
public RealmIdentity getRealmIdentity(Principal principal) throws RealmUnavailableException {
if (principal instanceof KeycloakPrincipal) {
return createRealmIdentity((KeycloakPrincipal) principal);
}
return RealmIdentity.NON_EXISTENT;
}
private RealmIdentity createRealmIdentity(KeycloakPrincipal principal) {
return new RealmIdentity() {
@Override
public Principal getRealmIdentityPrincipal() {
return principal;
}
@Override
public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType, String algorithmName) throws RealmUnavailableException {
return SupportLevel.UNSUPPORTED;
}
@Override
public <C extends Credential> C getCredential(Class<C> credentialType) throws RealmUnavailableException {
return null;
}
@Override
public SupportLevel getEvidenceVerifySupport(Class<? extends Evidence> evidenceType, String algorithmName) throws RealmUnavailableException {
return SupportLevel.SUPPORTED;
}
@Override
public boolean verifyEvidence(Evidence evidence) throws RealmUnavailableException {
return principal != null;
}
@Override
public boolean exists() throws RealmUnavailableException {
return principal != null;
}
@Override
public AuthorizationIdentity getAuthorizationIdentity() throws RealmUnavailableException {
RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) principal.getKeycloakSecurityContext();
Attributes attributes = new MapAttributes();
Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
attributes.addAll(RoleDecoder.KEY_ROLES, roles);
return AuthorizationIdentity.basicIdentity(attributes);
}
};
}
@Override
public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType, String algorithmName) throws RealmUnavailableException {
return SupportLevel.UNSUPPORTED;
}
@Override
public SupportLevel getEvidenceVerifySupport(Class<? extends Evidence> evidenceType, String algorithmName) throws RealmUnavailableException {
return SupportLevel.POSSIBLY_SUPPORTED;
}
}
@@ -1,82 +0,0 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2016 Red Hat, Inc., and individual 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.elytron;
import java.io.IOException;
import java.security.Principal;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.sasl.AuthorizeCallback;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.wildfly.security.auth.callback.AuthenticationCompleteCallback;
import org.wildfly.security.auth.callback.EvidenceVerifyCallback;
import org.wildfly.security.auth.callback.SecurityIdentityCallback;
import org.wildfly.security.auth.server.SecurityIdentity;
import org.wildfly.security.evidence.Evidence;
import org.wildfly.security.http.HttpAuthenticationException;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
final class SecurityIdentityUtil {
static final SecurityIdentity authorize(CallbackHandler callbackHandler, Principal principal) {
try {
EvidenceVerifyCallback evidenceVerifyCallback = new EvidenceVerifyCallback(new Evidence() {
@Override
public Principal getPrincipal() {
return principal;
}
});
callbackHandler.handle(new Callback[]{evidenceVerifyCallback});
if (evidenceVerifyCallback.isVerified()) {
AuthorizeCallback authorizeCallback = new AuthorizeCallback(null, null);
try {
callbackHandler.handle(new Callback[] {authorizeCallback});
authorizeCallback.isAuthorized();
} catch (Exception e) {
throw new HttpAuthenticationException(e);
}
SecurityIdentityCallback securityIdentityCallback = new SecurityIdentityCallback();
callbackHandler.handle(new Callback[]{AuthenticationCompleteCallback.SUCCEEDED, securityIdentityCallback});
SecurityIdentity securityIdentity = securityIdentityCallback.getSecurityIdentity();
return securityIdentity;
}
} catch (UnsupportedCallbackException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
return null;
}
}
@@ -1,19 +0,0 @@
#
# JBoss, Home of Professional Open Source.
# Copyright 2016 Red Hat, Inc., and individual 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.
#
org.keycloak.adapters.elytron.KeycloakHttpServerAuthenticationMechanismFactory
+2 -15
View File
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak WildFly Integration</name>
@@ -32,20 +32,7 @@
<modules>
<module>wildfly-adapter</module>
<module>wf8-subsystem</module>
<module>wildfly-subsystem</module>
</modules>
<profiles>
<profile>
<id>community</id>
<activation>
<property>
<name>!product</name>
</property>
</activation>
<modules>
<module>wf8-subsystem</module>
</modules>
</profile>
</profiles>
</project>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-parent</artifactId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
@@ -124,12 +124,6 @@ public class SharedAttributeDefinitons {
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition CORS_EXPOSED_HEADERS =
new SimpleAttributeDefinitionBuilder("cors-exposed-headers", ModelType.STRING, true)
.setXmlName("cors-exposed-headers")
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition EXPOSE_TOKEN =
new SimpleAttributeDefinitionBuilder("expose-token", ModelType.BOOLEAN, true)
.setXmlName("expose-token")
@@ -173,19 +167,7 @@ public class SharedAttributeDefinitons {
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition AUTODETECT_BEARER_ONLY =
new SimpleAttributeDefinitionBuilder("autodetect-bearer-only", ModelType.BOOLEAN, true)
.setXmlName("autodetect-bearer-only")
.setAllowExpression(true)
.setDefaultValue(new ModelNode(false))
.build();
protected static final SimpleAttributeDefinition IGNORE_OAUTH_QUERY_PARAMETER =
new SimpleAttributeDefinitionBuilder("ignore-oauth-query-parameter", ModelType.BOOLEAN, true)
.setXmlName("ignore-oauth-query-parameter")
.setAllowExpression(true)
.setDefaultValue(new ModelNode(false))
.build();
protected static final List<SimpleAttributeDefinition> ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
static {
@@ -204,7 +186,6 @@ public class SharedAttributeDefinitons {
ATTRIBUTES.add(CORS_MAX_AGE);
ATTRIBUTES.add(CORS_ALLOWED_HEADERS);
ATTRIBUTES.add(CORS_ALLOWED_METHODS);
ATTRIBUTES.add(CORS_EXPOSED_HEADERS);
ATTRIBUTES.add(EXPOSE_TOKEN);
ATTRIBUTES.add(AUTH_SERVER_URL_FOR_BACKEND_REQUESTS);
ATTRIBUTES.add(ALWAYS_REFRESH_TOKEN);
@@ -212,8 +193,6 @@ public class SharedAttributeDefinitons {
ATTRIBUTES.add(REGISTER_NODE_PERIOD);
ATTRIBUTES.add(TOKEN_STORE);
ATTRIBUTES.add(PRINCIPAL_ATTRIBUTE);
ATTRIBUTES.add(AUTODETECT_BEARER_ONLY);
ATTRIBUTES.add(IGNORE_OAUTH_QUERY_PARAMETER);
}
/**
@@ -39,7 +39,6 @@ keycloak.realm.client-key-password=n/a
keycloak.realm.cors-max-age=CORS max-age header
keycloak.realm.cors-allowed-headers=CORS allowed headers
keycloak.realm.cors-allowed-methods=CORS allowed methods
keycloak.realm.cors-exposed-headers=CORS exposed headers
keycloak.realm.expose-token=Enable secure URL that exposes access token
keycloak.realm.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
keycloak.realm.always-refresh-token=Refresh token on every single web request
@@ -47,8 +46,7 @@ keycloak.realm.register-node-at-startup=Cluster setting
keycloak.realm.register-node-period=how often to re-register node
keycloak.realm.token-store=cookie or session storage for auth session data
keycloak.realm.principal-attribute=token attribute to use to set Principal name
keycloak.realm.autodetect-bearer-only=autodetect bearer-only requests
keycloak.realm.ignore-oauth-query-parameter=disable query parameter parsing for access_token
keycloak.secure-deployment=A deployment secured by Keycloak
keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak
@@ -75,7 +73,6 @@ keycloak.secure-deployment.client-key-password=n/a
keycloak.secure-deployment.cors-max-age=CORS max-age header
keycloak.secure-deployment.cors-allowed-headers=CORS allowed headers
keycloak.secure-deployment.cors-allowed-methods=CORS allowed methods
keycloak.secure-deployment.cors-exposed-headers=CORS exposed headers
keycloak.secure-deployment.expose-token=Enable secure URL that exposes access token
keycloak.secure-deployment.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
keycloak.secure-deployment.always-refresh-token=Refresh token on every single web request
@@ -86,8 +83,6 @@ keycloak.secure-deployment.principal-attribute=token attribute to use to set Pri
keycloak.secure-deployment.turn-off-change-session-id-on-login=The session id is changed by default on a successful login. Change this to true if you want to turn this off
keycloak.secure-deployment.token-minimum-time-to-live=The adapter will refresh the token if the current token is expired OR will expire in 'token-minimum-time-to-live' seconds or less
keycloak.secure-deployment.min-time-between-jwks-requests=If adapter recognize token signed by unknown public key, it will try to download new public key from keycloak server. However it won't try to download if already tried it in less than 'min-time-between-jwks-requests' seconds
keycloak.secure-deployment.autodetect-bearer-only=autodetect bearer-only requests
keycloak.secure-deployment.ignore-oauth-query-parameter=disable query parameter parsing for access_token
keycloak.secure-deployment.credential=Credential value
@@ -58,7 +58,6 @@
<xs:element name="disable-trust-manager" type="xs:boolean" minOccurs="0" maxOccurs="1" />
<xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="cors-exposed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
@@ -66,8 +65,6 @@
<xs:element name="register-node-period" type="xs:integer" minOccurs="0" maxOccurs="1"/>
<xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="autodetect-bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="ignore-oauth-query-parameter" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
</xs:all>
<xs:attribute name="name" type="xs:string" use="required">
<xs:annotation>
@@ -90,7 +87,6 @@
<xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1" />
<xs:element name="cors-allowed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="cors-exposed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="resource" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="truststore" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="truststore-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
@@ -111,8 +107,6 @@
<xs:element name="turn-off-change-session-id-on-login" type="xs:boolean" minOccurs="0" maxOccurs="1" />
<xs:element name="token-minimum-time-to-live" type="xs:integer" minOccurs="0" maxOccurs="1"/>
<xs:element name="min-time-between-jwks-requests" type="xs:integer" minOccurs="0" maxOccurs="1"/>
<xs:element name="autodetect-bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="ignore-oauth-query-parameter" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
</xs:all>
<xs:attribute name="name" type="xs:string" use="required">
<xs:annotation>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-parent</artifactId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
@@ -37,8 +37,6 @@ import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD
public final class KeycloakAdapterConfigService {
private static final String CREDENTIALS_JSON_NAME = "credentials";
private static final String REDIRECT_REWRITE_RULE_JSON_NAME = "redirect-rewrite-rule";
private static final KeycloakAdapterConfigService INSTANCE = new KeycloakAdapterConfigService();
@@ -131,56 +129,6 @@ public final class KeycloakAdapterConfigService {
ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
return deployment.get(CREDENTIALS_JSON_NAME);
}
public void addRedirectRewriteRule(ModelNode operation, ModelNode model) {
ModelNode redirectRewritesRules = redirectRewriteRuleFromOp(operation);
if (!redirectRewritesRules.isDefined()) {
redirectRewritesRules = new ModelNode();
}
String redirectRewriteRuleName = redirectRewriteRule(operation);
if (!redirectRewriteRuleName.contains(".")) {
redirectRewritesRules.get(redirectRewriteRuleName).set(model.get("value").asString());
} else {
String[] parts = redirectRewriteRuleName.split("\\.");
String provider = parts[0];
String property = parts[1];
ModelNode redirectRewriteRule = redirectRewritesRules.get(provider);
if (!redirectRewriteRule.isDefined()) {
redirectRewriteRule = new ModelNode();
}
redirectRewriteRule.get(property).set(model.get("value").asString());
redirectRewritesRules.set(provider, redirectRewriteRule);
}
ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
deployment.get(REDIRECT_REWRITE_RULE_JSON_NAME).set(redirectRewritesRules);
}
public void removeRedirectRewriteRule(ModelNode operation) {
ModelNode redirectRewritesRules = redirectRewriteRuleFromOp(operation);
if (!redirectRewritesRules.isDefined()) {
throw new RuntimeException("Can not remove redirect rewrite rule. No rules defined for deployment in op " + operation.toString());
}
String ruleName = credentialNameFromOp(operation);
redirectRewritesRules.remove(ruleName);
}
public void updateRedirectRewriteRule(ModelNode operation, String attrName, ModelNode resolvedValue) {
ModelNode redirectRewritesRules = redirectRewriteRuleFromOp(operation);
if (!redirectRewritesRules.isDefined()) {
throw new RuntimeException("Can not update redirect rewrite rule. No rules defined for deployment in op " + operation.toString());
}
String ruleName = credentialNameFromOp(operation);
redirectRewritesRules.get(ruleName).set(resolvedValue);
}
private ModelNode redirectRewriteRuleFromOp(ModelNode operation) {
ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
return deployment.get(REDIRECT_REWRITE_RULE_JSON_NAME);
}
private String realmNameFromOp(ModelNode operation) {
return valueFromOpAddress(RealmDefinition.TAG_NAME, operation);
@@ -193,10 +141,6 @@ public final class KeycloakAdapterConfigService {
private String credentialNameFromOp(ModelNode operation) {
return valueFromOpAddress(CredentialDefinition.TAG_NAME, operation);
}
private String redirectRewriteRule(ModelNode operation) {
return valueFromOpAddress(RedirecRewritetRuleDefinition.TAG_NAME, operation);
}
private String valueFromOpAddress(String addrElement, ModelNode operation) {
String deploymentName = getValueOfAddrElement(operation.get(ADDRESS), addrElement);
@@ -48,7 +48,6 @@ public class KeycloakExtension implements Extension {
static final RealmDefinition REALM_DEFINITION = new RealmDefinition();
static final SecureDeploymentDefinition SECURE_DEPLOYMENT_DEFINITION = new SecureDeploymentDefinition();
static final CredentialDefinition CREDENTIAL_DEFINITION = new CredentialDefinition();
static final RedirecRewritetRuleDefinition REDIRECT_RULE_DEFINITON = new RedirecRewritetRuleDefinition();
public static StandardResourceDescriptionResolver getResourceDescriptionResolver(final String... keyPrefix) {
StringBuilder prefix = new StringBuilder(SUBSYSTEM_NAME);
@@ -78,7 +77,6 @@ public class KeycloakExtension implements Extension {
registration.registerSubModel(REALM_DEFINITION);
ManagementResourceRegistration secureDeploymentRegistration = registration.registerSubModel(SECURE_DEPLOYMENT_DEFINITION);
secureDeploymentRegistration.registerSubModel(CREDENTIAL_DEFINITION);
secureDeploymentRegistration.registerSubModel(REDIRECT_RULE_DEFINITON);
subsystem.registerXMLElementWriter(PARSER);
}
@@ -96,17 +96,12 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
PathElement.pathElement(SecureDeploymentDefinition.TAG_NAME, name));
addSecureDeployment.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
List<ModelNode> credentialsToAdd = new ArrayList<ModelNode>();
List<ModelNode> redirectRulesToAdd = new ArrayList<ModelNode>();
while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
String tagName = reader.getLocalName();
if (tagName.equals(CredentialDefinition.TAG_NAME)) {
readCredential(reader, addr, credentialsToAdd);
continue;
}
if (tagName.equals(RedirecRewritetRuleDefinition.TAG_NAME)) {
readRewriteRule(reader, addr, redirectRulesToAdd);
continue;
}
SimpleAttributeDefinition def = SecureDeploymentDefinition.lookup(tagName);
if (def == null) throw new XMLStreamException("Unknown secure-deployment tag " + tagName);
@@ -116,7 +111,6 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
// Must add credentials after the deployment is added.
resourcesToAdd.add(addSecureDeployment);
resourcesToAdd.addAll(credentialsToAdd);
resourcesToAdd.addAll(redirectRulesToAdd);
}
public void readCredential(XMLExtendedStreamReader reader, PathAddress parent, List<ModelNode> credentialsToAdd) throws XMLStreamException {
@@ -155,43 +149,6 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
}
}
}
public void readRewriteRule(XMLExtendedStreamReader reader, PathAddress parent, List<ModelNode> rewriteRuleToToAdd) throws XMLStreamException {
String name = readNameAttribute(reader);
Map<String, String> values = new HashMap<>();
String textValue = null;
while (reader.hasNext()) {
int next = reader.next();
if (next == CHARACTERS) {
// text value of redirect rule element
String text = reader.getText();
if (text == null || text.trim().isEmpty()) {
continue;
}
textValue = text;
} else if (next == START_ELEMENT) {
String key = reader.getLocalName();
reader.next();
String value = reader.getText();
reader.next();
values.put(key, value);
} else if (next == END_ELEMENT) {
break;
}
}
if (textValue != null) {
ModelNode addRedirectRule = getRedirectRuleToAdd(parent, name, textValue);
rewriteRuleToToAdd.add(addRedirectRule);
} else {
for (Map.Entry<String, String> entry : values.entrySet()) {
ModelNode addRedirectRule = getRedirectRuleToAdd(parent, name + "." + entry.getKey(), entry.getValue());
rewriteRuleToToAdd.add(addRedirectRule);
}
}
}
private ModelNode getCredentialToAdd(PathAddress parent, String name, String value) {
ModelNode addCredential = new ModelNode();
@@ -201,15 +158,6 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
addCredential.get(CredentialDefinition.VALUE.getName()).set(value);
return addCredential;
}
private ModelNode getRedirectRuleToAdd(PathAddress parent, String name, String value) {
ModelNode addRedirectRule = new ModelNode();
addRedirectRule.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
PathAddress addr = PathAddress.pathAddress(parent, PathElement.pathElement(RedirecRewritetRuleDefinition.TAG_NAME, name));
addRedirectRule.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
addRedirectRule.get(RedirecRewritetRuleDefinition.VALUE.getName()).set(value);
return addRedirectRule;
}
// expects that the current tag will have one single attribute called "name"
private String readNameAttribute(XMLExtendedStreamReader reader) throws XMLStreamException {
@@ -271,11 +219,6 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
if (credentials.isDefined()) {
writeCredentials(writer, credentials);
}
ModelNode redirectRewriteRule = deploymentElements.get(RedirecRewritetRuleDefinition.TAG_NAME);
if (redirectRewriteRule.isDefined()) {
writeRedirectRules(writer, redirectRewriteRule);
}
writer.writeEndElement();
}
@@ -322,34 +265,6 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
writer.writeEndElement();
}
}
private void writeRedirectRules(XMLExtendedStreamWriter writer, ModelNode redirectRules) throws XMLStreamException {
Map<String, Object> parsed = new LinkedHashMap<>();
for (Property redirectRule : redirectRules.asPropertyList()) {
String ruleName = redirectRule.getName();
String ruleValue = redirectRule.getValue().get(RedirecRewritetRuleDefinition.VALUE.getName()).asString();
parsed.put(ruleName, ruleValue);
}
for (Map.Entry<String, Object> entry : parsed.entrySet()) {
writer.writeStartElement(RedirecRewritetRuleDefinition.TAG_NAME);
writer.writeAttribute("name", entry.getKey());
Object value = entry.getValue();
if (value instanceof String) {
writeCharacters(writer, (String) value);
} else {
Map<String, String> redirectRulesProps = (Map<String, String>) value;
for (Map.Entry<String, String> prop : redirectRulesProps.entrySet()) {
writer.writeStartElement(prop.getKey());
writeCharacters(writer, prop.getValue());
writer.writeEndElement();
}
}
writer.writeEndElement();
}
}
// code taken from org.jboss.as.controller.AttributeMarshaller
private void writeCharacters(XMLExtendedStreamWriter writer, String content) throws XMLStreamException {
@@ -1,61 +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.
*/
package org.keycloak.subsystem.adapter.extension;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
import org.jboss.as.controller.SimpleResourceDefinition;
import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
import org.jboss.as.controller.operations.validation.StringLengthValidator;
import org.jboss.as.controller.registry.ManagementResourceRegistration;
import org.jboss.dmr.ModelType;
/**
*
* @author sblanc
*/
public class RedirecRewritetRuleDefinition extends SimpleResourceDefinition {
public static final String TAG_NAME = "redirect-rewrite-rule";
protected static final AttributeDefinition VALUE =
new SimpleAttributeDefinitionBuilder("value", ModelType.STRING, false)
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, false, true))
.build();
public RedirecRewritetRuleDefinition() {
super(PathElement.pathElement(TAG_NAME),
KeycloakExtension.getResourceDescriptionResolver(TAG_NAME),
new RedirectRewriteRuleAddHandler(VALUE),
RedirectRewriteRuleRemoveHandler.INSTANCE);
}
@Override
public void registerOperations(ManagementResourceRegistration resourceRegistration) {
super.registerOperations(resourceRegistration);
resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
}
@Override
public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
super.registerAttributes(resourceRegistration);
resourceRegistration.registerReadWriteAttribute(VALUE, null, new RedirectRewriteRuleReadWriteAttributeHandler());
}
}
@@ -1,38 +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.
*/
package org.keycloak.subsystem.adapter.extension;
import org.jboss.as.controller.AbstractAddStepHandler;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.dmr.ModelNode;
public class RedirectRewriteRuleAddHandler extends AbstractAddStepHandler {
public RedirectRewriteRuleAddHandler(AttributeDefinition... attributes) {
super(attributes);
}
@Override
protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
ckService.addRedirectRewriteRule(operation, context.resolveExpressions(model));
}
}
@@ -1,44 +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.
*/
package org.keycloak.subsystem.adapter.extension;
import org.jboss.as.controller.AbstractWriteAttributeHandler;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.dmr.ModelNode;
public class RedirectRewriteRuleReadWriteAttributeHandler extends AbstractWriteAttributeHandler<KeycloakAdapterConfigService> {
@Override
protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
ModelNode resolvedValue, ModelNode currentValue, AbstractWriteAttributeHandler.HandbackHolder<KeycloakAdapterConfigService> hh) throws OperationFailedException {
KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
ckService.updateRedirectRewriteRule(operation, attributeName, resolvedValue);
hh.setHandback(ckService);
return false;
}
@Override
protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
ModelNode valueToRestore, ModelNode valueToRevert, KeycloakAdapterConfigService ckService) throws OperationFailedException {
ckService.updateRedirectRewriteRule(operation, attributeName, valueToRestore);
}
}
@@ -1,36 +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.
*/
package org.keycloak.subsystem.adapter.extension;
import org.jboss.as.controller.AbstractRemoveStepHandler;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.dmr.ModelNode;
public class RedirectRewriteRuleRemoveHandler extends AbstractRemoveStepHandler {
public static RedirectRewriteRuleRemoveHandler INSTANCE = new RedirectRewriteRuleRemoveHandler();
private RedirectRewriteRuleRemoveHandler() {}
@Override
protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
ckService.removeRedirectRewriteRule(operation);
}
}
@@ -124,12 +124,6 @@ public class SharedAttributeDefinitons {
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition CORS_EXPOSED_HEADERS =
new SimpleAttributeDefinitionBuilder("cors-exposed-headers", ModelType.STRING, true)
.setXmlName("cors-exposed-headers")
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition EXPOSE_TOKEN =
new SimpleAttributeDefinitionBuilder("expose-token", ModelType.BOOLEAN, true)
.setXmlName("expose-token")
@@ -172,20 +166,6 @@ public class SharedAttributeDefinitons {
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition AUTODETECT_BEARER_ONLY =
new SimpleAttributeDefinitionBuilder("autodetect-bearer-only", ModelType.BOOLEAN, true)
.setXmlName("autodetect-bearer-only")
.setAllowExpression(true)
.setDefaultValue(new ModelNode(false))
.build();
protected static final SimpleAttributeDefinition IGNORE_OAUTH_QUERY_PARAMETER =
new SimpleAttributeDefinitionBuilder("ignore-oauth-query-parameter", ModelType.BOOLEAN, true)
.setXmlName("ignore-oauth-query-parameter")
.setAllowExpression(true)
.setDefaultValue(new ModelNode(false))
.build();
@@ -206,7 +186,6 @@ public class SharedAttributeDefinitons {
ATTRIBUTES.add(CORS_MAX_AGE);
ATTRIBUTES.add(CORS_ALLOWED_HEADERS);
ATTRIBUTES.add(CORS_ALLOWED_METHODS);
ATTRIBUTES.add(CORS_EXPOSED_HEADERS);
ATTRIBUTES.add(EXPOSE_TOKEN);
ATTRIBUTES.add(AUTH_SERVER_URL_FOR_BACKEND_REQUESTS);
ATTRIBUTES.add(ALWAYS_REFRESH_TOKEN);
@@ -214,8 +193,6 @@ public class SharedAttributeDefinitons {
ATTRIBUTES.add(REGISTER_NODE_PERIOD);
ATTRIBUTES.add(TOKEN_STORE);
ATTRIBUTES.add(PRINCIPAL_ATTRIBUTE);
ATTRIBUTES.add(AUTODETECT_BEARER_ONLY);
ATTRIBUTES.add(IGNORE_OAUTH_QUERY_PARAMETER);
}
private static boolean isSet(ModelNode attributes, SimpleAttributeDefinition def) {
@@ -39,7 +39,6 @@ keycloak.realm.client-key-password=n/a
keycloak.realm.cors-max-age=CORS max-age header
keycloak.realm.cors-allowed-headers=CORS allowed headers
keycloak.realm.cors-allowed-methods=CORS allowed methods
keycloak.realm.cors-exposed-headers=CORS exposed headers
keycloak.realm.expose-token=Enable secure URL that exposes access token
keycloak.realm.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
keycloak.realm.always-refresh-token=Refresh token on every single web request
@@ -47,8 +46,7 @@ keycloak.realm.register-node-at-startup=Cluster setting
keycloak.realm.register-node-period=how often to re-register node
keycloak.realm.token-store=cookie or session storage for auth session data
keycloak.realm.principal-attribute=token attribute to use to set Principal name
keycloak.realm.autodetect-bearer-only=autodetect bearer-only requests
keycloak.realm.ignore-oauth-query-parameter=disable query parameter parsing for access_token
keycloak.secure-deployment=A deployment secured by Keycloak
keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak
@@ -65,19 +63,16 @@ keycloak.secure-deployment.connection-pool-size=Connection pool size for the cli
keycloak.secure-deployment.resource=Application name
keycloak.secure-deployment.use-resource-role-mappings=Use resource level permissions from token
keycloak.secure-deployment.credentials=Adapter credentials
keycloak.secure-deployment.redirect-rewrite-rule=Apply a rewrite rule for the redirect URI
keycloak.secure-deployment.bearer-only=Bearer Token Auth only
keycloak.secure-deployment.enable-basic-auth=Enable Basic Authentication
keycloak.secure-deployment.public-client=Public client
keycloak.secure-deployment.enable-cors=Enable Keycloak CORS support
keycloak.secure-deployment.autodetect-bearer-only=autodetect bearer-only requests
keycloak.secure-deployment.client-keystore=n/a
keycloak.secure-deployment.client-keystore-password=n/a
keycloak.secure-deployment.client-key-password=n/a
keycloak.secure-deployment.cors-max-age=CORS max-age header
keycloak.secure-deployment.cors-allowed-headers=CORS allowed headers
keycloak.secure-deployment.cors-allowed-methods=CORS allowed methods
keycloak.secure-deployment.cors-exposed-headers=CORS exposed headers
keycloak.secure-deployment.expose-token=Enable secure URL that exposes access token
keycloak.secure-deployment.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
keycloak.secure-deployment.always-refresh-token=Refresh token on every single web request
@@ -88,16 +83,10 @@ keycloak.secure-deployment.principal-attribute=token attribute to use to set Pri
keycloak.secure-deployment.turn-off-change-session-id-on-login=The session id is changed by default on a successful login. Change this to true if you want to turn this off
keycloak.secure-deployment.token-minimum-time-to-live=The adapter will refresh the token if the current token is expired OR will expire in 'token-minimum-time-to-live' seconds or less
keycloak.secure-deployment.min-time-between-jwks-requests=If adapter recognize token signed by unknown public key, it will try to download new public key from keycloak server. However it won't try to download if already tried it in less than 'min-time-between-jwks-requests' seconds
keycloak.secure-deployment.ignore-oauth-query-parameter=disable query parameter parsing for access_token
keycloak.secure-deployment.credential=Credential value
keycloak.credential=Credential
keycloak.credential.value=Credential value
keycloak.credential.add=Credential add
keycloak.credential.remove=Credential remove
keycloak.redirect-rewrite-rule=redirect-rewrite-rule
keycloak.redirect-rewrite-rule.value=redirect-rewrite-rule value
keycloak.redirect-rewrite-rule.add=redirect-rewrite-rule add
keycloak.redirect-rewrite-rule.remove=redirect-rewrite-rule remove
keycloak.credential.remove=Credential remove
@@ -58,7 +58,6 @@
<xs:element name="disable-trust-manager" type="xs:boolean" minOccurs="0" maxOccurs="1" />
<xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="cors-exposed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
@@ -66,8 +65,6 @@
<xs:element name="register-node-period" type="xs:integer" minOccurs="0" maxOccurs="1"/>
<xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="autodetect-bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="ignore-oauth-query-parameter" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
</xs:all>
<xs:attribute name="name" type="xs:string" use="required">
<xs:annotation>
@@ -90,7 +87,6 @@
<xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1" />
<xs:element name="cors-allowed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="cors-exposed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="resource" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="truststore" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="truststore-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
@@ -101,7 +97,6 @@
<xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:element name="credential" type="credential-type" minOccurs="1" maxOccurs="1"/>
<xs:element name="redirect-rewrite-rule" type="redirect-rewrite-rule-type" minOccurs="1" maxOccurs="1"/>
<xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="register-node-at-startup" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
@@ -112,8 +107,6 @@
<xs:element name="turn-off-change-session-id-on-login" type="xs:boolean" minOccurs="0" maxOccurs="1" />
<xs:element name="token-minimum-time-to-live" type="xs:integer" minOccurs="0" maxOccurs="1"/>
<xs:element name="min-time-between-jwks-requests" type="xs:integer" minOccurs="0" maxOccurs="1"/>
<xs:element name="autodetect-bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="ignore-oauth-query-parameter" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
</xs:all>
<xs:attribute name="name" type="xs:string" use="required">
<xs:annotation>
@@ -128,10 +121,4 @@
</xs:sequence>
<xs:attribute name="name" type="xs:string" use="required" />
</xs:complexType>
<xs:complexType name="redirect-rewrite-rule-type" mixed="true">
<xs:sequence maxOccurs="unbounded" minOccurs="0">
<xs:any processContents="lax"></xs:any>
</xs:sequence>
<xs:attribute name="name" type="xs:string" use="required" />
</xs:complexType>
</xs:schema>
@@ -53,7 +53,6 @@
<auth-server-url>http://localhost:8080/auth</auth-server-url>
<ssl-required>EXTERNAL</ssl-required>
<credential name="secret">0aa31d98-e0aa-404c-b6e0-e771dba1e798</credential>
<redirect-rewrite-rule name="^/wsmaster/api/(.*)$">api/$1/</redirect-rewrite-rule>
</secure-deployment>
<secure-deployment name="http-endpoint">
<realm>master</realm>
@@ -67,6 +66,5 @@
<credential name="jwt">
<client-keystore-file>/tmp/keystore.jks</client-keystore-file>
</credential>
<redirect-rewrite-rule name="^/wsmaster/api/(.*)$">/api/$1/</redirect-rewrite-rule>
</secure-deployment>
</subsystem>
+1 -1
View File
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../pom.xml</relativePath>
</parent>
<name>Keycloak Adapters</name>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-saml-eap-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -13
View File
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.1.Final</version>
<version>3.0.0.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak SAML EAP Integration</name>
@@ -30,18 +30,6 @@
<artifactId>keycloak-saml-eap-integration-pom</artifactId>
<packaging>pom</packaging>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jboss.as</groupId>
<artifactId>jboss-as-parent</artifactId>
<version>${jboss.as.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<modules>
<module>adapter</module>
<module>subsystem</module>
Loaded 100 of 2481 files, more files were not shown because too many files have changed in this diff. Show more