Compare commits

..
Author SHA1 Message Date
keycloak-bot e418390a2a Set version to 2.4.0.Final 2016-11-23 19:28:10 +00:00
keycloak-bot 1570d222a1 Set version to 2.4.0.Final-SNAPSHOT 2016-11-21 21:02:35 +00:00
keycloak-bot f0a1cb4f26 Set version to 2.4.0.CR1 2016-11-21 19:29:18 +00:00
3649 changed files with 74204 additions and 403143 deletions

No files matched your search

-1
View File
@@ -14,7 +14,6 @@
nbactions.xml nbactions.xml
nb-configuration.xml nb-configuration.xml
catalog.xml catalog.xml
nbproject
# Compiled source # # Compiled source #
################### ###################
+3 -3
View File
@@ -12,11 +12,11 @@ env:
- MAVEN_SKIP_RC=true - MAVEN_SKIP_RC=true
- MAVEN_OPTS="-Xms512m -Xmx2048m" - MAVEN_OPTS="-Xms512m -Xmx2048m"
matrix: matrix:
- TESTS=old
- TESTS=group1 - TESTS=group1
- TESTS=group2 - TESTS=group2
- TESTS=group3 - TESTS=group3
- TESTS=group4 - TESTS=adapter
- TESTS=old
jdk: jdk:
- oraclejdk8 - oraclejdk8
@@ -25,7 +25,7 @@ before_script:
- export MAVEN_SKIP_RC=true - export MAVEN_SKIP_RC=true
install: install:
- travis_wait 60 mvn install --no-snapshot-updates -Pdistribution -DskipTestsuite -B -V -q - travis_wait 60 mvn install -Pdistribution -DskipTests=true -B -V -q
script: script:
- ./travis-run-tests.sh $TESTS - ./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 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 tar xfz distribution/appliance-dist/target/keycloak-appliance-dist-all-<VERSION>.tar.gz
cd keycloak-<VERSION> cd keycloak-appliance-dist-all-<VERSION>/keycloak
bin/standalone.sh bin/standalone.sh
To stop the server press `Ctrl + C`. To stop the server press `Ctrl + C`.
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -342,12 +342,6 @@ public class AdapterDeploymentContext {
return delegate.getNotBefore(); return delegate.getNotBefore();
} }
@Override
public void updateNotBefore(int notBefore) {
delegate.setNotBefore(notBefore);
getPublicKeyLocator().reset(this);
}
@Override @Override
public void setExposeToken(boolean exposeToken) { public void setExposeToken(boolean exposeToken) {
delegate.setExposeToken(exposeToken); delegate.setExposeToken(exposeToken);
@@ -452,16 +446,6 @@ public class AdapterDeploymentContext {
public int getMinTimeBetweenJwksRequests() { public int getMinTimeBetweenJwksRequests() {
return delegate.getMinTimeBetweenJwksRequests(); return delegate.getMinTimeBetweenJwksRequests();
} }
@Override
public int getPublicKeyCacheTtl() {
return delegate.getPublicKeyCacheTtl();
}
@Override
public void setPublicKeyCacheTtl(int publicKeyCacheTtl) {
delegate.setPublicKeyCacheTtl(publicKeyCacheTtl);
}
} }
protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) { protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) {
@@ -101,7 +101,6 @@ public class AuthenticatedActionsHandler {
if (!deployment.isCors()) return false; if (!deployment.isCors()) return false;
KeycloakSecurityContext securityContext = facade.getSecurityContext(); KeycloakSecurityContext securityContext = facade.getSecurityContext();
String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN); String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN);
String exposeHeaders = deployment.getCorsExposedHeaders();
String requestOrigin = UriUtils.getOrigin(facade.getRequest().getURI()); String requestOrigin = UriUtils.getOrigin(facade.getRequest().getURI());
log.debugv("Origin: {0} uri: {1}", origin, facade.getRequest().getURI()); log.debugv("Origin: {0} uri: {1}", origin, facade.getRequest().getURI());
if (securityContext != null && origin != null && !origin.equals(requestOrigin)) { if (securityContext != null && origin != null && !origin.equals(requestOrigin)) {
@@ -125,9 +124,6 @@ public class AuthenticatedActionsHandler {
facade.getResponse().setStatus(200); facade.getResponse().setStatus(200);
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin); facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
if (exposeHeaders != null) {
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, exposeHeaders);
}
} else { } else {
log.debugv("cors validation not needed as we're not a secure session or origin header was null: {0}", facade.getRequest().getURI()); 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 ORIGIN = "Origin";
String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method"; String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
} }
@@ -46,7 +46,14 @@ public class HttpAdapterUtils {
} }
InputStream is = entity.getContent(); InputStream is = entity.getContent();
try { try {
return JsonSerialization.readValue(is, clazz); ByteArrayOutputStream os = new ByteArrayOutputStream();
int c;
while ((c = is.read()) != -1) {
os.write(c);
}
byte[] bytes = os.toByteArray();
String json = new String(bytes);
return JsonSerialization.readValue(json, clazz);
} finally { } finally {
try { try {
is.close(); is.close();
@@ -170,8 +170,8 @@ public class HttpClientBuilder {
return this; return this;
} }
public HttpClientBuilder disableCookieCache(boolean disable) { public HttpClientBuilder disableCookieCache() {
this.disableCookieCache = disable; this.disableCookieCache = true;
return this; return this;
} }
@@ -334,7 +334,7 @@ public class HttpClientBuilder {
} }
public HttpClient build(AdapterHttpClientConfig adapterConfig) { 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(); String truststorePath = adapterConfig.getTruststore();
if (truststorePath != null) { 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: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 $ * @version $Revision: 1 $
*/ */
public class KeycloakDeployment { public class KeycloakDeployment {
@@ -59,7 +57,6 @@ public class KeycloakDeployment {
protected String resourceName; protected String resourceName;
protected boolean bearerOnly; protected boolean bearerOnly;
protected boolean autodetectBearerOnly;
protected boolean enableBasicAuth; protected boolean enableBasicAuth;
protected boolean publicClient; protected boolean publicClient;
protected Map<String, Object> resourceCredentials = new HashMap<>(); protected Map<String, Object> resourceCredentials = new HashMap<>();
@@ -75,7 +72,6 @@ public class KeycloakDeployment {
protected int corsMaxAge = -1; protected int corsMaxAge = -1;
protected String corsAllowedHeaders; protected String corsAllowedHeaders;
protected String corsAllowedMethods; protected String corsAllowedMethods;
protected String corsExposedHeaders;
protected boolean exposeToken; protected boolean exposeToken;
protected boolean alwaysRefreshToken; protected boolean alwaysRefreshToken;
protected boolean registerNodeAtStartup; protected boolean registerNodeAtStartup;
@@ -85,15 +81,8 @@ public class KeycloakDeployment {
protected volatile int notBefore; protected volatile int notBefore;
protected int tokenMinimumTimeToLive; protected int tokenMinimumTimeToLive;
protected int minTimeBetweenJwksRequests; protected int minTimeBetweenJwksRequests;
protected int publicKeyCacheTtl;
private PolicyEnforcer policyEnforcer; private PolicyEnforcer policyEnforcer;
// https://tools.ietf.org/html/rfc7636
protected boolean pkce = false;
protected boolean ignoreOAuthQueryParameter;
protected Map<String, String> redirectRewriteRules;
public KeycloakDeployment() { public KeycloakDeployment() {
} }
@@ -211,14 +200,6 @@ public class KeycloakDeployment {
this.bearerOnly = bearerOnly; this.bearerOnly = bearerOnly;
} }
public boolean isAutodetectBearerOnly() {
return autodetectBearerOnly;
}
public void setAutodetectBearerOnly(boolean autodetectBearerOnly) {
this.autodetectBearerOnly = autodetectBearerOnly;
}
public boolean isEnableBasicAuth() { public boolean isEnableBasicAuth() {
return enableBasicAuth; return enableBasicAuth;
} }
@@ -331,14 +312,6 @@ public class KeycloakDeployment {
this.corsAllowedMethods = corsAllowedMethods; this.corsAllowedMethods = corsAllowedMethods;
} }
public String getCorsExposedHeaders() {
return corsExposedHeaders;
}
public void setCorsExposedHeaders(String corsExposedHeaders) {
this.corsExposedHeaders = corsExposedHeaders;
}
public boolean isExposeToken() { public boolean isExposeToken() {
return exposeToken; return exposeToken;
} }
@@ -355,11 +328,6 @@ public class KeycloakDeployment {
this.notBefore = notBefore; this.notBefore = notBefore;
} }
public void updateNotBefore(int notBefore) {
this.notBefore = notBefore;
getPublicKeyLocator().reset(this);
}
public boolean isAlwaysRefreshToken() { public boolean isAlwaysRefreshToken() {
return alwaysRefreshToken; return alwaysRefreshToken;
} }
@@ -416,14 +384,6 @@ public class KeycloakDeployment {
this.minTimeBetweenJwksRequests = minTimeBetweenJwksRequests; this.minTimeBetweenJwksRequests = minTimeBetweenJwksRequests;
} }
public int getPublicKeyCacheTtl() {
return publicKeyCacheTtl;
}
public void setPublicKeyCacheTtl(int publicKeyCacheTtl) {
this.publicKeyCacheTtl = publicKeyCacheTtl;
}
public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) { public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) {
this.policyEnforcer = policyEnforcer; this.policyEnforcer = policyEnforcer;
} }
@@ -431,31 +391,4 @@ public class KeycloakDeployment {
public PolicyEnforcer getPolicyEnforcer() { public PolicyEnforcer getPolicyEnforcer() {
return policyEnforcer; 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: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 $ * @version $Revision: 1 $
*/ */
public class KeycloakDeploymentBuilder { public class KeycloakDeploymentBuilder {
@@ -98,25 +96,15 @@ public class KeycloakDeploymentBuilder {
deployment.setCorsMaxAge(adapterConfig.getCorsMaxAge()); deployment.setCorsMaxAge(adapterConfig.getCorsMaxAge());
deployment.setCorsAllowedHeaders(adapterConfig.getCorsAllowedHeaders()); deployment.setCorsAllowedHeaders(adapterConfig.getCorsAllowedHeaders());
deployment.setCorsAllowedMethods(adapterConfig.getCorsAllowedMethods()); deployment.setCorsAllowedMethods(adapterConfig.getCorsAllowedMethods());
deployment.setCorsExposedHeaders(adapterConfig.getCorsExposedHeaders());
}
// https://tools.ietf.org/html/rfc7636
if (adapterConfig.isPkce()) {
deployment.setPkce(true);
} }
deployment.setBearerOnly(adapterConfig.isBearerOnly()); deployment.setBearerOnly(adapterConfig.isBearerOnly());
deployment.setAutodetectBearerOnly(adapterConfig.isAutodetectBearerOnly());
deployment.setEnableBasicAuth(adapterConfig.isEnableBasicAuth()); deployment.setEnableBasicAuth(adapterConfig.isEnableBasicAuth());
deployment.setAlwaysRefreshToken(adapterConfig.isAlwaysRefreshToken()); deployment.setAlwaysRefreshToken(adapterConfig.isAlwaysRefreshToken());
deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup()); deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup());
deployment.setRegisterNodePeriod(adapterConfig.getRegisterNodePeriod()); deployment.setRegisterNodePeriod(adapterConfig.getRegisterNodePeriod());
deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive()); deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive());
deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests()); deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests());
deployment.setPublicKeyCacheTtl(adapterConfig.getPublicKeyCacheTtl());
deployment.setIgnoreOAuthQueryParameter(adapterConfig.isIgnoreOAuthQueryParameter());
deployment.setRewriteRedirectRules(adapterConfig.getRedirectRewriteRules());
if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) { if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) {
throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url"); throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");
@@ -37,10 +37,7 @@ import org.keycloak.representations.IDToken;
import org.keycloak.util.TokenUtil; import org.keycloak.util.TokenUtil;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException; import java.util.concurrent.atomic.AtomicLong;
import java.net.URL;
import java.util.Map;
import java.util.logging.Level;
/** /**
@@ -143,7 +140,6 @@ public class OAuthRequestAuthenticator {
protected String getRedirectUri(String state) { protected String getRedirectUri(String state) {
String url = getRequestUrl(); String url = getRequestUrl();
log.debugf("callback uri: %s", url); log.debugf("callback uri: %s", url);
if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) { if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
int port = sslRedirectPort(); int port = sslRedirectPort();
if (port < 0) { if (port < 0) {
@@ -173,7 +169,7 @@ public class OAuthRequestAuthenticator {
KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone() KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone()
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE) .queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName()) .queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
.queryParam(OAuth2Constants.REDIRECT_URI, rewrittenRedirectUri(url)) .queryParam(OAuth2Constants.REDIRECT_URI, url)
.queryParam(OAuth2Constants.STATE, state) .queryParam(OAuth2Constants.STATE, state)
.queryParam("login", "true"); .queryParam("login", "true");
if(loginHint != null && loginHint.length() > 0){ if(loginHint != null && loginHint.length() > 0){
@@ -199,8 +195,10 @@ public class OAuthRequestAuthenticator {
return sslRedirectPort; return sslRedirectPort;
} }
protected static final AtomicLong counter = new AtomicLong();
protected String getStateCode() { protected String getStateCode() {
return AdapterUtils.generateId(); return counter.getAndIncrement() + "/" + AdapterUtils.generateId();
} }
protected AuthChallenge loginRedirect() { protected AuthChallenge loginRedirect() {
@@ -323,11 +321,10 @@ public class OAuthRequestAuthenticator {
AccessTokenResponse tokenResponse = null; AccessTokenResponse tokenResponse = null;
strippedOauthParametersRequestUri = stripOauthParametersFromRedirect(); strippedOauthParametersRequestUri = stripOauthParametersFromRedirect();
try { try {
// For COOKIE store we don't have httpSessionId and single sign-out won't be available // 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; 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) { } catch (ServerRequest.HttpFailure failure) {
log.error("failed to turn code into token"); log.error("failed to turn code into token");
log.error("status from server: " + failure.getStatus()); log.error("status from server: " + failure.getStatus());
@@ -360,7 +357,7 @@ public class OAuthRequestAuthenticator {
return challenge(403, OIDCAuthenticationError.Reason.INVALID_TOKEN, null); return challenge(403, OIDCAuthenticationError.Reason.INVALID_TOKEN, null);
} }
if (tokenResponse.getNotBeforePolicy() > deployment.getNotBefore()) { if (tokenResponse.getNotBeforePolicy() > deployment.getNotBefore()) {
deployment.updateNotBefore(tokenResponse.getNotBeforePolicy()); deployment.setNotBefore(tokenResponse.getNotBeforePolicy());
} }
if (token.getIssuedAt() < deployment.getNotBefore()) { if (token.getIssuedAt() < deployment.getNotBefore()) {
log.error("Stale token"); log.error("Stale token");
@@ -380,22 +377,5 @@ public class OAuthRequestAuthenticator {
return builder.build().toString(); 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;
}
} }
@@ -155,7 +155,7 @@ public class PreAuthActionsHandler {
} else { } else {
log.debugf("logout of all sessions for application '%s'", action.getResource()); log.debugf("logout of all sessions for application '%s'", action.getResource());
if (action.getNotBefore() > deployment.getNotBefore()) { if (action.getNotBefore() > deployment.getNotBefore()) {
deployment.updateNotBefore(action.getNotBefore()); deployment.setNotBefore(action.getNotBefore());
} }
userSessionManagement.logoutAll(); userSessionManagement.logoutAll();
} }
@@ -177,7 +177,7 @@ public class PreAuthActionsHandler {
} }
PushNotBeforeAction action = JsonSerialization.readValue(token.getContent(), PushNotBeforeAction.class); PushNotBeforeAction action = JsonSerialization.readValue(token.getContent(), PushNotBeforeAction.class);
if (!validateAction(action)) return; if (!validateAction(action)) return;
deployment.updateNotBefore(action.getNotBefore()); deployment.setNotBefore(action.getNotBefore());
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -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: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 $ * @version $Revision: 1 $
*/ */
public class QueryParamterTokenRequestAuthenticator extends BearerTokenRequestAuthenticator { public class QueryParamterTokenRequestAuthenticator extends BearerTokenRequestAuthenticator {
@@ -35,9 +33,6 @@ public class QueryParamterTokenRequestAuthenticator extends BearerTokenRequestAu
} }
public AuthOutcome authenticate(HttpFacade exchange) { public AuthOutcome authenticate(HttpFacade exchange) {
if(!deployment.isOAuthQueryParameterEnabled()) {
return AuthOutcome.NOT_ATTEMPTED;
}
tokenString = null; tokenString = null;
tokenString = getAccessTokenFromQueryParamter(exchange); tokenString = getAccessTokenFromQueryParamter(exchange);
if (tokenString == null || tokenString.trim().isEmpty()) { if (tokenString == null || tokenString.trim().isEmpty()) {
@@ -144,7 +144,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
} }
if (response.getNotBeforePolicy() > deployment.getNotBefore()) { if (response.getNotBeforePolicy() > deployment.getNotBefore()) {
deployment.updateNotBefore(response.getNotBeforePolicy()); deployment.setNotBefore(response.getNotBeforePolicy());
} }
this.token = token; this.token = token;
@@ -17,14 +17,11 @@
package org.keycloak.adapters; package org.keycloak.adapters;
import java.util.Collections;
import java.util.List;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal; import org.keycloak.KeycloakPrincipal;
import org.keycloak.adapters.spi.AuthChallenge; import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome; import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.common.enums.SslRequired;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -119,12 +116,6 @@ public abstract class RequestAuthenticator {
return AuthOutcome.NOT_ATTEMPTED; return AuthOutcome.NOT_ATTEMPTED;
} }
if (isAutodetectedBearerOnly(facade.getRequest())) {
challenge = bearer.getChallenge();
log.debug("NOT_ATTEMPTED: Treating as bearer only");
return AuthOutcome.NOT_ATTEMPTED;
}
if (log.isTraceEnabled()) { if (log.isTraceEnabled()) {
log.trace("try oauth"); log.trace("try oauth");
} }
@@ -161,43 +152,12 @@ public abstract class RequestAuthenticator {
protected boolean verifySSL() { protected boolean verifySSL() {
if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) { if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
log.warnf("SSL is required to authenticate. Remote address %s is secure: %s, SSL required for: %s .", log.warn("SSL is required to authenticate");
facade.getRequest().getRemoteAddr(), facade.getRequest().isSecure(), deployment.getSslRequired().name());
return true; return true;
} }
return false; return false;
} }
protected boolean isAutodetectedBearerOnly(HttpFacade.Request request) {
if (!deployment.isAutodetectBearerOnly()) return false;
String headerValue = facade.getRequest().getHeader("X-Requested-With");
if (headerValue != null && headerValue.equalsIgnoreCase("XMLHttpRequest")) {
return true;
}
headerValue = facade.getRequest().getHeader("Faces-Request");
if (headerValue != null && headerValue.startsWith("partial/")) {
return true;
}
headerValue = facade.getRequest().getHeader("SOAPAction");
if (headerValue != null) {
return true;
}
List<String> accepts = facade.getRequest().getHeaders("Accept");
if (accepts == null) accepts = Collections.emptyList();
for (String accept : accepts) {
if (accept.contains("text/html") || accept.contains("text/*") || accept.contains("*/*")) {
return false;
}
}
return true;
}
protected abstract OAuthRequestAuthenticator createOAuthAuthenticator(); protected abstract OAuthRequestAuthenticator createOAuthAuthenticator();
protected BearerTokenRequestAuthenticator createBearerTokenAuthenticator() { protected BearerTokenRequestAuthenticator createBearerTokenAuthenticator() {
@@ -33,8 +33,6 @@ import org.keycloak.constants.AdapterConstants;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import org.jboss.logging.Logger;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@@ -48,8 +46,6 @@ import java.util.List;
*/ */
public class ServerRequest { public class ServerRequest {
private static Logger logger = Logger.getLogger(ServerRequest.class);
public static class HttpFailure extends Exception { public static class HttpFailure extends Exception {
private int status; private int status;
private String error; 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 { public static AccessTokenResponse invokeRefresh(KeycloakDeployment deployment, String refreshToken) throws IOException, HttpFailure {
List<NameValuePair> formparams = new ArrayList<NameValuePair>(); List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN)); formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN));
@@ -17,25 +17,28 @@
*/ */
package org.keycloak.adapters.authorization; package org.keycloak.adapters.authorization;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.AuthorizationContext; import org.keycloak.AuthorizationContext;
import org.keycloak.KeycloakSecurityContext; import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.OIDCHttpFacade; import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.spi.HttpFacade.Request; 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.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.AccessToken;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig; import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode; import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig; import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
import org.keycloak.representations.idm.authorization.Permission; import org.keycloak.representations.idm.authorization.Permission;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/ */
@@ -45,7 +48,7 @@ public abstract class AbstractPolicyEnforcer {
private final PolicyEnforcerConfig enforcerConfig; private final PolicyEnforcerConfig enforcerConfig;
private final PolicyEnforcer policyEnforcer; private final PolicyEnforcer policyEnforcer;
private Map<String, PathConfig> paths; private List<PathConfig> paths;
private AuthzClient authzClient; private AuthzClient authzClient;
private PathMatcher pathMatcher; private PathMatcher pathMatcher;
@@ -53,8 +56,8 @@ public abstract class AbstractPolicyEnforcer {
this.policyEnforcer = policyEnforcer; this.policyEnforcer = policyEnforcer;
this.enforcerConfig = policyEnforcer.getEnforcerConfig(); this.enforcerConfig = policyEnforcer.getEnforcerConfig();
this.authzClient = policyEnforcer.getClient(); this.authzClient = policyEnforcer.getClient();
this.pathMatcher = policyEnforcer.getPathMatcher(); this.pathMatcher = new PathMatcher();
this.paths = policyEnforcer.getPaths(); this.paths = new ArrayList<>(policyEnforcer.getPaths());
} }
public AuthorizationContext authorize(OIDCHttpFacade httpFacade) { public AuthorizationContext authorize(OIDCHttpFacade httpFacade) {
@@ -71,23 +74,20 @@ public abstract class AbstractPolicyEnforcer {
if (accessToken != null) { if (accessToken != null) {
Request request = httpFacade.getRequest(); Request request = httpFacade.getRequest();
String path = getPath(request); Response response = httpFacade.getResponse();
String pathInfo = URI.create(request.getURI()).getPath().substring(1);
String path = pathInfo.substring(pathInfo.indexOf('/'), pathInfo.length());
PathConfig pathConfig = this.pathMatcher.matches(path, this.paths); PathConfig pathConfig = this.pathMatcher.matches(path, this.paths);
LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig); LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig);
if (pathConfig == null) { if (pathConfig == null) {
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) { if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
return createAuthorizationContext(accessToken, null); return createAuthorizationContext(accessToken);
} }
LOGGER.debugf("Could not find a configuration for path [%s]", path); LOGGER.debugf("Could not find a configuration for path [%s]", path);
response.sendError(403, "Could not find a configuration for path [" + path + "].");
if (isDefaultAccessDeniedUri(request, enforcerConfig)) {
return createAuthorizationContext(accessToken, null);
}
handleAccessDenied(httpFacade);
return createEmptyAuthorizationContext(false); return createEmptyAuthorizationContext(false);
} }
@@ -96,21 +96,20 @@ public abstract class AbstractPolicyEnforcer {
return createEmptyAuthorizationContext(true); 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 { try {
return createAuthorizationContext(accessToken, pathConfig); return createAuthorizationContext(accessToken);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("Error processing path [" + pathConfig.getPath() + "].", e); throw new RuntimeException("Error processing path [" + actualPathConfig.getPath() + "].", e);
} }
} }
if (!challenge(actualPathConfig, requiredScopes, httpFacade)) {
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig); LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
response.sendError(403, "Authorization failed.");
if (!challenge(pathConfig, requiredScopes, httpFacade)) {
LOGGER.debugf("Challenge not sent, sending default forbidden response. Path [%s]", pathConfig);
handleAccessDenied(httpFacade);
} }
} }
} }
@@ -123,10 +122,13 @@ public abstract class AbstractPolicyEnforcer {
protected boolean isAuthorized(PathConfig actualPathConfig, Set<String> requiredScopes, AccessToken accessToken, OIDCHttpFacade httpFacade) { protected boolean isAuthorized(PathConfig actualPathConfig, Set<String> requiredScopes, AccessToken accessToken, OIDCHttpFacade httpFacade) {
Request request = httpFacade.getRequest(); Request request = httpFacade.getRequest();
PolicyEnforcerConfig enforcerConfig = getEnforcerConfig(); PolicyEnforcerConfig enforcerConfig = getEnforcerConfig();
String accessDeniedPath = enforcerConfig.getOnDenyRedirectTo();
if (isDefaultAccessDeniedUri(request, enforcerConfig)) { if (accessDeniedPath != null) {
if (request.getURI().contains(accessDeniedPath)) {
return true; return true;
} }
}
AccessToken.Authorization authorization = accessToken.getAuthorization(); AccessToken.Authorization authorization = accessToken.getAuthorization();
@@ -171,21 +173,6 @@ public abstract class AbstractPolicyEnforcer {
return false; return false;
} }
protected void handleAccessDenied(OIDCHttpFacade httpFacade) {
httpFacade.getResponse().sendError(403);
}
private boolean isDefaultAccessDeniedUri(Request request, PolicyEnforcerConfig enforcerConfig) {
String accessDeniedPath = enforcerConfig.getOnDenyRedirectTo();
if (accessDeniedPath != null) {
if (request.getURI().contains(accessDeniedPath)) {
return true;
}
}
return false;
}
private boolean hasResourceScopePermission(Set<String> requiredScopes, Permission permission, PathConfig actualPathConfig) { private boolean hasResourceScopePermission(Set<String> requiredScopes, Permission permission, PathConfig actualPathConfig) {
Set<String> allowedScopes = permission.getScopes(); Set<String> allowedScopes = permission.getScopes();
return (allowedScopes.containsAll(requiredScopes) || allowedScopes.isEmpty()); return (allowedScopes.containsAll(requiredScopes) || allowedScopes.isEmpty());
@@ -204,7 +191,7 @@ public abstract class AbstractPolicyEnforcer {
} }
private AuthorizationContext createEmptyAuthorizationContext(final boolean granted) { private AuthorizationContext createEmptyAuthorizationContext(final boolean granted) {
return new ClientAuthorizationContext(authzClient) { return new AuthorizationContext() {
@Override @Override
public boolean hasPermission(String resourceName, String scopeName) { public boolean hasPermission(String resourceName, String scopeName) {
return granted; return granted;
@@ -232,8 +219,34 @@ public abstract class AbstractPolicyEnforcer {
}; };
} }
private String getPath(Request request) { private PathConfig resolvePathConfig(PathConfig originalConfig, Request request) {
return request.getRelativePath(); if (originalConfig.hasPattern()) {
String pathInfo = URI.create(request.getURI()).getPath().substring(1);
String path = pathInfo.substring(pathInfo.indexOf('/'), pathInfo.length());
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 = new PathConfig();
config.setId(targetResource.getId());
config.setName(targetResource.getName());
config.setType(targetResource.getType());
config.setPath(targetResource.getUri());
config.setScopes(originalConfig.getScopes());
config.setMethods(originalConfig.getMethods());
config.setParentConfig(originalConfig);
config.setEnforcementMode(originalConfig.getEnforcementMode());
this.paths.add(config);
return config;
}
}
return originalConfig;
} }
private Set<String> getRequiredScopes(PathConfig pathConfig, Request request) { private Set<String> getRequiredScopes(PathConfig pathConfig, Request request) {
@@ -252,8 +265,8 @@ public abstract class AbstractPolicyEnforcer {
return requiredScopes; return requiredScopes;
} }
private AuthorizationContext createAuthorizationContext(AccessToken accessToken, PathConfig pathConfig) { private AuthorizationContext createAuthorizationContext(AccessToken accessToken) {
return new ClientAuthorizationContext(accessToken, pathConfig, this.paths, authzClient); return new AuthorizationContext(accessToken, this.paths);
} }
private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) { private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
@@ -17,8 +17,6 @@
*/ */
package org.keycloak.adapters.authorization; package org.keycloak.adapters.authorization;
import java.util.Set;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.adapters.OIDCHttpFacade; import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.spi.HttpFacade; 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.authorization.client.resource.ProtectionResource;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig; import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
import java.util.Set;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/ */
@@ -52,26 +52,20 @@ public class BearerTokenPolicyEnforcer extends AbstractPolicyEnforcer {
private void challengeEntitlementAuthentication(OIDCHttpFacade facade) { private void challengeEntitlementAuthentication(OIDCHttpFacade facade) {
HttpFacade.Response response = facade.getResponse(); HttpFacade.Response response = facade.getResponse();
AuthzClient authzClient = getAuthzClient(); AuthzClient authzClient = getAuthzClient();
String clientId = authzClient.getConfiguration().getResource(); String clientId = authzClient.getConfiguration().getClientId();
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/entitlement"; String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/entitlement";
response.setStatus(401); response.setStatus(401);
response.setHeader("WWW-Authenticate", "KC_ETT realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\""); 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) { private void challengeUmaAuthentication(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
HttpFacade.Response response = facade.getResponse(); HttpFacade.Response response = facade.getResponse();
AuthzClient authzClient = getAuthzClient(); AuthzClient authzClient = getAuthzClient();
String ticket = getPermissionTicket(pathConfig, requiredScopes, authzClient); String ticket = getPermissionTicket(pathConfig, requiredScopes, authzClient);
String clientId = authzClient.getConfiguration().getResource(); String clientId = authzClient.getConfiguration().getClientId();
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/authorize"; String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/authorize";
response.setStatus(401); response.setStatus(401);
response.setHeader("WWW-Authenticate", "UMA realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\",ticket=\"" + ticket + "\""); 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) { private String getPermissionTicket(PathConfig pathConfig, Set<String> requiredScopes, AuthzClient authzClient) {
@@ -17,10 +17,6 @@
*/ */
package org.keycloak.adapters.authorization; package org.keycloak.adapters.authorization;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OIDCHttpFacade; import org.keycloak.adapters.OIDCHttpFacade;
@@ -38,6 +34,10 @@ import org.keycloak.representations.AccessToken;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig; import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
import org.keycloak.representations.idm.authorization.Permission; import org.keycloak.representations.idm.authorization.Permission;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/ */
@@ -51,8 +51,10 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
@Override @Override
protected boolean isAuthorized(PathConfig pathConfig, Set<String> requiredScopes, AccessToken accessToken, OIDCHttpFacade httpFacade) { protected boolean isAuthorized(PathConfig pathConfig, Set<String> requiredScopes, AccessToken accessToken, OIDCHttpFacade httpFacade) {
int retry = 2;
AccessToken original = accessToken; AccessToken original = accessToken;
while (retry > 0) {
if (super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade)) { if (super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade)) {
return true; return true;
} }
@@ -78,17 +80,14 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
original.setAuthorization(authorization); original.setAuthorization(authorization);
return super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade); retry--;
}
return false;
} }
@Override @Override
protected boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) { protected boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
handleAccessDenied(facade);
return true;
}
@Override
protected void handleAccessDenied(OIDCHttpFacade facade) {
String accessDeniedPath = getEnforcerConfig().getOnDenyRedirectTo(); String accessDeniedPath = getEnforcerConfig().getOnDenyRedirectTo();
HttpFacade.Response response = facade.getResponse(); HttpFacade.Response response = facade.getResponse();
@@ -98,6 +97,8 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
} else { } else {
response.sendError(403); response.sendError(403);
} }
return true;
} }
private AccessToken requestAuthorizationToken(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade httpFacade) { private AccessToken requestAuthorizationToken(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade httpFacade) {
@@ -127,7 +128,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
AccessToken token = httpFacade.getSecurityContext().getToken(); AccessToken token = httpFacade.getSecurityContext().getToken();
if (token.getAuthorization() == null) { 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); return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
} else { } else {
EntitlementRequest request = new EntitlementRequest(); EntitlementRequest request = new EntitlementRequest();
@@ -135,14 +136,12 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
permissionRequest.setResourceSetId(pathConfig.getId()); permissionRequest.setResourceSetId(pathConfig.getId());
permissionRequest.setResourceSetName(pathConfig.getName()); permissionRequest.setResourceSetName(pathConfig.getName());
permissionRequest.setScopes(new HashSet<>(pathConfig.getScopes())); 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); 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); return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
} }
} }
} catch (AuthorizationDeniedException e) { } catch (AuthorizationDeniedException e) {
LOGGER.debug("Authorization denied", e);
return null; return null;
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("Unexpected error during authorization request.", e); throw new RuntimeException("Unexpected error during authorization request.", e);
@@ -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,87 @@
*/ */
package org.keycloak.adapters.authorization; 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 org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
import java.util.List;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/ */
class PathMatcher { class PathMatcher {
private static final char WILDCARD = '*'; private static final String ANY_RESOURCE_PATTERN = "/*";
private final AuthzClient authzClient;
// TODO: make this configurable
private PathCache cache = new PathCache(100, 30000);
public PathMatcher(AuthzClient authzClient) { PathConfig matches(final String requestedUri, List<PathConfig> paths) {
this.authzClient = authzClient; PathConfig actualConfig = null;
for (PathConfig entry : paths) {
String protectedUri = entry.getPath();
String selectedUri = null;
if (protectedUri.equals(ANY_RESOURCE_PATTERN) && actualConfig == null) {
selectedUri = protectedUri;
} }
public PathConfig matches(final String targetUri, Map<String, PathConfig> paths) { int suffixIndex = protectedUri.indexOf(ANY_RESOURCE_PATTERN + ".");
PathConfig pathConfig = paths.get(targetUri) == null ? cache.get(targetUri) : paths.get(targetUri);
if (pathConfig != null) {
return pathConfig;
}
PathConfig matchingAnyPath = null;
PathConfig matchingAnySuffixPath = null;
for (PathConfig entry : paths.values()) {
String expectedUri = entry.getPath();
String matchingUri = null;
if (exactMatch(expectedUri, targetUri, expectedUri)) {
matchingUri = expectedUri;
}
if (isTemplate(expectedUri)) {
String templateUri = buildUriFromTemplate(expectedUri, targetUri);
if (templateUri != null) {
if (exactMatch(expectedUri, targetUri, templateUri)) {
matchingUri = templateUri;
entry = resolvePathConfig(entry, targetUri);
}
}
}
if (matchingUri != null) {
StringBuilder path = new StringBuilder(expectedUri);
int patternIndex = path.indexOf("/" + WILDCARD);
if (patternIndex != -1) {
path.delete(patternIndex, path.length());
}
patternIndex = path.indexOf("{");
if (patternIndex != -1) {
path.delete(patternIndex, path.length());
}
String pathString = path.toString();
if ("".equals(pathString)) {
pathString = "/";
}
if (matchingUri.equals(targetUri)) {
cache.put(targetUri, entry);
return entry;
}
if (WILDCARD == expectedUri.charAt(expectedUri.length() - 1)) {
matchingAnyPath = entry;
} else {
int suffixIndex = expectedUri.indexOf(WILDCARD + ".");
if (suffixIndex != -1) { if (suffixIndex != -1) {
String protectedSuffix = expectedUri.substring(suffixIndex + 1); String protectedSuffix = protectedUri.substring(suffixIndex + ANY_RESOURCE_PATTERN.length());
if (targetUri.endsWith(protectedSuffix)) { if (requestedUri.endsWith(protectedSuffix)) {
matchingAnySuffixPath = entry; selectedUri = protectedUri;
}
}
}
}
}
if (matchingAnySuffixPath != null) {
cache.put(targetUri, matchingAnySuffixPath);
return matchingAnySuffixPath;
}
if (matchingAnyPath != null) {
cache.put(targetUri, matchingAnyPath);
}
return matchingAnyPath;
}
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); if (protectedUri.equals(requestedUri)) {
selectedUri = protectedUri;
}
if (protectedUri.endsWith(ANY_RESOURCE_PATTERN)) {
String formattedPattern = removeWildCardsFromUri(protectedUri);
if (!formattedPattern.equals("/") && requestedUri.startsWith(formattedPattern)) {
selectedUri = protectedUri;
}
if (!formattedPattern.equals("/") && formattedPattern.endsWith("/") && formattedPattern.substring(0, formattedPattern.length() - 1).equals(requestedUri)) {
selectedUri = protectedUri;
}
}
int startRegex = protectedUri.indexOf('{');
if (startRegex != -1) {
String prefix = protectedUri.substring(0, startRegex);
if (requestedUri.startsWith(prefix)) {
selectedUri = protectedUri;
}
}
if (selectedUri != null) {
selectedUri = protectedUri;
}
if (selectedUri != null) {
if (actualConfig == null) {
actualConfig = entry;
} else { } else {
if (c == '/') { if (actualConfig.equals(ANY_RESOURCE_PATTERN)) {
paramIndex++; actualConfig = entry;
}
if (protectedUri.startsWith(removeWildCardsFromUri(actualConfig.getPath()))) {
actualConfig = entry;
}
} }
matchingUri[matchingUriLastIndex++] = c;
} }
} }
if (matchingUri[matchingUri.length - 1] == '\u0000') { return actualConfig;
return null;
} }
return String.valueOf(matchingUri); private String removeWildCardsFromUri(String protectedUri) {
} return protectedUri.replaceAll("/[*]", "/");
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;
} }
} }
@@ -21,9 +21,7 @@ import org.jboss.logging.Logger;
import org.keycloak.AuthorizationContext; import org.keycloak.AuthorizationContext;
import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OIDCHttpFacade; import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.authorization.client.AuthzClient; import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.ClientAuthenticator;
import org.keycloak.authorization.client.Configuration; import org.keycloak.authorization.client.Configuration;
import org.keycloak.authorization.client.representation.RegistrationResponse; import org.keycloak.authorization.client.representation.RegistrationResponse;
import org.keycloak.authorization.client.representation.ResourceRepresentation; import org.keycloak.authorization.client.representation.ResourceRepresentation;
@@ -36,10 +34,8 @@ import org.keycloak.representations.idm.authorization.Permission;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
/** /**
@@ -52,24 +48,17 @@ public class PolicyEnforcer {
private final KeycloakDeployment deployment; private final KeycloakDeployment deployment;
private final AuthzClient authzClient; private final AuthzClient authzClient;
private final PolicyEnforcerConfig enforcerConfig; private final PolicyEnforcerConfig enforcerConfig;
private final Map<String, PathConfig> paths; private final List<PathConfig> paths;
private final PathMatcher pathMatcher;
public PolicyEnforcer(KeycloakDeployment deployment, AdapterConfig adapterConfig) { public PolicyEnforcer(KeycloakDeployment deployment, AdapterConfig adapterConfig) {
this.deployment = deployment; this.deployment = deployment;
this.enforcerConfig = adapterConfig.getPolicyEnforcerConfig(); this.enforcerConfig = adapterConfig.getPolicyEnforcerConfig();
this.authzClient = AuthzClient.create(new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), deployment.getClient()), new ClientAuthenticator() { this.authzClient = AuthzClient.create(new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), deployment.getClient()));
@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.paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig); this.paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig);
if (LOGGER.isDebugEnabled()) { if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Initialization complete. Path configurations:"); LOGGER.debug("Initialization complete. Path configurations:");
for (PathConfig pathConfig : this.paths.values()) { for (PathConfig pathConfig : this.paths) {
LOGGER.debug(pathConfig); LOGGER.debug(pathConfig);
} }
} }
@@ -107,19 +96,15 @@ public class PolicyEnforcer {
return authzClient; return authzClient;
} }
public Map<String, PathConfig> getPaths() { public List<PathConfig> getPaths() {
return paths; return Collections.unmodifiableList(paths);
}
void addPath(PathConfig pathConfig) {
paths.put(pathConfig.getPath(), pathConfig);
} }
KeycloakDeployment getDeployment() { KeycloakDeployment getDeployment() {
return deployment; return deployment;
} }
private Map<String, PathConfig> configurePaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) { private List<PathConfig> configurePaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
boolean loadPathsFromServer = true; boolean loadPathsFromServer = true;
for (PathConfig pathConfig : enforcerConfig.getPaths()) { for (PathConfig pathConfig : enforcerConfig.getPaths()) {
@@ -138,8 +123,8 @@ public class PolicyEnforcer {
} }
} }
private Map<String, PathConfig> configureDefinedPaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) { private List<PathConfig> configureDefinedPaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>()); List<PathConfig> paths = new ArrayList<>();
for (PathConfig pathConfig : enforcerConfig.getPaths()) { for (PathConfig pathConfig : enforcerConfig.getPaths()) {
Set<String> search; Set<String> search;
@@ -187,7 +172,7 @@ public class PolicyEnforcer {
PathConfig existingPath = null; PathConfig existingPath = null;
for (PathConfig current : paths.values()) { for (PathConfig current : paths) {
if (current.getId().equals(pathConfig.getId()) && current.getPath().equals(pathConfig.getPath())) { if (current.getId().equals(pathConfig.getId()) && current.getPath().equals(pathConfig.getPath())) {
existingPath = current; existingPath = current;
break; break;
@@ -195,7 +180,7 @@ public class PolicyEnforcer {
} }
if (existingPath == null) { if (existingPath == null) {
paths.put(pathConfig.getPath(), pathConfig); paths.add(pathConfig);
} else { } else {
existingPath.getMethods().addAll(pathConfig.getMethods()); existingPath.getMethods().addAll(pathConfig.getMethods());
existingPath.getScopes().addAll(pathConfig.getScopes()); existingPath.getScopes().addAll(pathConfig.getScopes());
@@ -205,36 +190,28 @@ public class PolicyEnforcer {
return paths; return paths;
} }
private Map<String, PathConfig> configureAllPathsForResourceServer(ProtectedResource protectedResource) { private List<PathConfig> configureAllPathsForResourceServer(ProtectedResource protectedResource) {
LOGGER.info("Querying the server for all resources associated with this application."); LOGGER.info("Querying the server for all resources associated with this application.");
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>()); List<PathConfig> paths = new ArrayList<>();
for (String id : protectedResource.findAll()) { for (String id : protectedResource.findAll()) {
RegistrationResponse response = protectedResource.findById(id); RegistrationResponse response = protectedResource.findById(id);
ResourceRepresentation resourceDescription = response.getResourceDescription(); ResourceRepresentation resourceDescription = response.getResourceDescription();
if (resourceDescription.getUri() != null) { if (resourceDescription.getUri() != null) {
PathConfig pathConfig = createPathConfig(resourceDescription); paths.add(createPathConfig(resourceDescription));
paths.put(pathConfig.getPath(), pathConfig);
} }
} }
return paths; return paths;
} }
static PathConfig createPathConfig(ResourceRepresentation resourceDescription) { private PathConfig createPathConfig(ResourceRepresentation resourceDescription) {
PathConfig pathConfig = new PathConfig(); PathConfig pathConfig = new PathConfig();
pathConfig.setId(resourceDescription.getId()); pathConfig.setId(resourceDescription.getId());
pathConfig.setName(resourceDescription.getName()); pathConfig.setName(resourceDescription.getName());
pathConfig.setPath(resourceDescription.getUri());
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);
List<String> scopeNames = new ArrayList<>(); List<String> scopeNames = new ArrayList<>();
@@ -247,8 +224,4 @@ public class PolicyEnforcer {
return pathConfig; return pathConfig;
} }
public PathMatcher getPathMatcher() {
return pathMatcher;
}
} }
@@ -37,9 +37,4 @@ public class HardcodedPublicKeyLocator implements PublicKeyLocator {
public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) { public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
return publicKey; return publicKey;
} }
@Override
public void reset(KeycloakDeployment deployment) {
}
} }
@@ -25,6 +25,7 @@ import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import org.keycloak.jose.jwk.JSONWebKeySet; import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.jose.jwk.JWK; import org.keycloak.jose.jwk.JWK;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.util.JWKSUtils; import org.keycloak.util.JWKSUtils;
import java.security.PublicKey; import java.security.PublicKey;
@@ -47,51 +48,36 @@ public class JWKPublicKeyLocator implements PublicKeyLocator {
@Override @Override
public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) { public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
int minTimeBetweenRequests = deployment.getMinTimeBetweenJwksRequests(); int minTimeBetweenRequests = deployment.getMinTimeBetweenJwksRequests();
int publicKeyCacheTtl = deployment.getPublicKeyCacheTtl();
int currentTime = Time.currentTime();
// Check if key is in cache. // Check if key is in cache.
PublicKey publicKey = lookupCachedKey(publicKeyCacheTtl, currentTime, kid); PublicKey publicKey = currentKeys.get(kid);
if (publicKey != null) { if (publicKey != null) {
return publicKey; return publicKey;
} }
int currentTime = Time.currentTime();
// Check if we are allowed to send request // Check if we are allowed to send request
if (currentTime > lastRequestTime + minTimeBetweenRequests) {
synchronized (this) { synchronized (this) {
currentTime = Time.currentTime(); currentTime = Time.currentTime();
if (currentTime > lastRequestTime + minTimeBetweenRequests) { if (currentTime > lastRequestTime + minTimeBetweenRequests) {
sendRequest(deployment); sendRequest(deployment);
lastRequestTime = currentTime; lastRequestTime = currentTime;
} else { } else {
log.debug("Won't send request to realm jwks url. Last request time was " + lastRequestTime); log.debugf("Won't send request to realm jwks url. Last request time was %d", lastRequestTime);
} }
return lookupCachedKey(publicKeyCacheTtl, currentTime, kid);
} }
} }
@Override
public void reset(KeycloakDeployment deployment) {
synchronized (this) {
sendRequest(deployment);
lastRequestTime = Time.currentTime();
}
}
private PublicKey lookupCachedKey(int publicKeyCacheTtl, int currentTime, String kid) {
if (lastRequestTime + publicKeyCacheTtl > currentTime) {
return currentKeys.get(kid); return currentKeys.get(kid);
} else {
return null;
}
} }
private void sendRequest(KeycloakDeployment deployment) { private void sendRequest(KeycloakDeployment deployment) {
if (log.isTraceEnabled()) { if (log.isTraceEnabled()) {
log.trace("Going to send request to retrieve new set of realm public keys for client " + deployment.getResourceName()); log.tracef("Going to send request to retrieve new set of realm public keys for client %s", deployment.getResourceName());
} }
HttpGet getMethod = new HttpGet(deployment.getJwksUrl()); HttpGet getMethod = new HttpGet(deployment.getJwksUrl());
@@ -101,7 +87,7 @@ public class JWKPublicKeyLocator implements PublicKeyLocator {
Map<String, PublicKey> publicKeys = JWKSUtils.getKeysForUse(jwks, JWK.Use.SIG); Map<String, PublicKey> publicKeys = JWKSUtils.getKeysForUse(jwks, JWK.Use.SIG);
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("Realm public keys successfully retrieved for client " + deployment.getResourceName() + ". New kids: " + publicKeys.keySet().toString()); log.debugf("Realm public keys successfully retrieved for client %s. New kids: %s", deployment.getResourceName(), publicKeys.keySet().toString());
} }
// Update current keys // Update current keys
@@ -34,11 +34,4 @@ public interface PublicKeyLocator {
*/ */
PublicKey getPublicKey(String kid, KeycloakDeployment deployment); PublicKey getPublicKey(String kid, KeycloakDeployment deployment);
/**
* Reset the state of locator (eg. clear the cached keys)
*
* @param deployment
*/
void reset(KeycloakDeployment deployment);
} }
@@ -29,13 +29,10 @@ import org.keycloak.common.util.PemUtils;
import org.keycloak.enums.TokenStore; import org.keycloak.enums.TokenStore;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @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 { public class KeycloakDeploymentBuilderTest {
@@ -56,12 +53,10 @@ public class KeycloakDeploymentBuilderTest {
assertEquals(1000, deployment.getCorsMaxAge()); assertEquals(1000, deployment.getCorsMaxAge());
assertEquals("POST, PUT, DELETE, GET", deployment.getCorsAllowedMethods()); assertEquals("POST, PUT, DELETE, GET", deployment.getCorsAllowedMethods());
assertEquals("X-Custom, X-Custom2", deployment.getCorsAllowedHeaders()); assertEquals("X-Custom, X-Custom2", deployment.getCorsAllowedHeaders());
assertEquals("X-Custom3, X-Custom4", deployment.getCorsExposedHeaders());
assertTrue(deployment.isBearerOnly()); assertTrue(deployment.isBearerOnly());
assertTrue(deployment.isPublicClient()); assertTrue(deployment.isPublicClient());
assertTrue(deployment.isEnableBasicAuth()); assertTrue(deployment.isEnableBasicAuth());
assertTrue(deployment.isExposeToken()); assertTrue(deployment.isExposeToken());
assertFalse(deployment.isOAuthQueryParameterEnabled());
assertEquals("234234-234234-234234", deployment.getResourceCredentials().get("secret")); assertEquals("234234-234234-234234", deployment.getResourceCredentials().get("secret"));
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId()); assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
assertEquals(20, ((ThreadSafeClientConnManager) deployment.getClient().getConnectionManager()).getMaxTotal()); assertEquals(20, ((ThreadSafeClientConnManager) deployment.getClient().getConnectionManager()).getMaxTotal());
@@ -74,8 +69,6 @@ public class KeycloakDeploymentBuilderTest {
assertEquals("email", deployment.getPrincipalAttribute()); assertEquals("email", deployment.getPrincipalAttribute());
assertEquals(10, deployment.getTokenMinimumTimeToLive()); assertEquals(10, deployment.getTokenMinimumTimeToLive());
assertEquals(20, deployment.getMinTimeBetweenJwksRequests()); assertEquals(20, deployment.getMinTimeBetweenJwksRequests());
assertEquals(120, deployment.getPublicKeyCacheTtl());
assertEquals("/api/$1", deployment.getRedirectRewriteRules().get("^/wsmaster/api/(.*)$"));
} }
@Test @Test
@@ -85,7 +78,6 @@ public class KeycloakDeploymentBuilderTest {
assertTrue(deployment.getPublicKeyLocator() instanceof JWKPublicKeyLocator); assertTrue(deployment.getPublicKeyLocator() instanceof JWKPublicKeyLocator);
assertEquals(10, deployment.getMinTimeBetweenJwksRequests()); assertEquals(10, deployment.getMinTimeBetweenJwksRequests());
assertEquals(86400, deployment.getPublicKeyCacheTtl());
} }
@Test @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-max-age": 1000,
"cors-allowed-methods": "POST, PUT, DELETE, GET", "cors-allowed-methods": "POST, PUT, DELETE, GET",
"cors-allowed-headers": "X-Custom, X-Custom2", "cors-allowed-headers": "X-Custom, X-Custom2",
"cors-exposed-headers": "X-Custom3, X-Custom4",
"bearer-only": true, "bearer-only": true,
"public-client": true, "public-client": true,
"enable-basic-auth": true, "enable-basic-auth": true,
@@ -31,10 +30,5 @@
"token-store": "cookie", "token-store": "cookie",
"principal-attribute": "email", "principal-attribute": "email",
"token-minimum-time-to-live": 10, "token-minimum-time-to-live": 10,
"min-time-between-jwks-requests": 20, "min-time-between-jwks-requests": 20
"public-key-cache-ttl": 120,
"ignore-oauth-query-parameter": true,
"redirect-rewrite-rules" : {
"^/wsmaster/api/(.*)$" : "/api/$1"
}
} }
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-as7-integration-pom</artifactId> <artifactId>keycloak-as7-integration-pom</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-as7-integration-pom</artifactId> <artifactId>keycloak-as7-integration-pom</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
+3 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-as7-integration-pom</artifactId> <artifactId>keycloak-as7-integration-pom</artifactId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
@@ -88,6 +88,7 @@
<dependency> <dependency>
<groupId>org.jboss.logging</groupId> <groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId> <artifactId>jboss-logging</artifactId>
<version>3.1.0.GA</version>
</dependency> </dependency>
<dependency> <dependency>
@@ -103,6 +104,7 @@ projects that depend on this project.-->
<dependency> <dependency>
<groupId>org.jboss.msc</groupId> <groupId>org.jboss.msc</groupId>
<artifactId>jboss-msc</artifactId> <artifactId>jboss-msc</artifactId>
<version>1.0.2.GA</version>
</dependency> </dependency>
<dependency> <dependency>
+1 -13
View File
@@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<name>Keycloak AS7 / JBoss EAP 6 Integration</name> <name>Keycloak AS7 / JBoss EAP 6 Integration</name>
@@ -30,18 +30,6 @@
<artifactId>keycloak-as7-integration-pom</artifactId> <artifactId>keycloak-as7-integration-pom</artifactId>
<packaging>pom</packaging> <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> <modules>
<module>as7-adapter-spi</module> <module>as7-adapter-spi</module>
<module>as7-adapter</module> <module>as7-adapter</module>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -66,11 +66,6 @@ public class JaxrsHttpFacade implements OIDCHttpFacade {
return requestContext.getUriInfo().getRequestUri().toString(); return requestContext.getUriInfo().getRequestUri().toString();
} }
@Override
public String getRelativePath() {
return requestContext.getUriInfo().getPath();
}
@Override @Override
public boolean isSecure() { public boolean isSecure() {
return securityContext.isSecure(); return securityContext.isSecure();
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -106,8 +106,6 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
public abstract AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment); public abstract AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment);
public abstract JettyUserSessionManagement createSessionManagement(Request request);
public void logoutCurrent(Request request) { public void logoutCurrent(Request request) {
AdapterDeploymentContext deploymentContext = (AdapterDeploymentContext) request.getAttribute(AdapterDeploymentContext.class.getName()); AdapterDeploymentContext deploymentContext = (AdapterDeploymentContext) request.getAttribute(AdapterDeploymentContext.class.getName());
KeycloakSecurityContext ksc = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName()); KeycloakSecurityContext ksc = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
@@ -289,7 +287,7 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
log.debug("*** deployment isn't configured return false"); log.debug("*** deployment isn't configured return false");
return Authentication.UNAUTHENTICATED; return Authentication.UNAUTHENTICATED;
} }
PreAuthActionsHandler handler = new PreAuthActionsHandler(createSessionManagement(request), deploymentContext, facade); PreAuthActionsHandler handler = new PreAuthActionsHandler(new JettyUserSessionManagement(request.getSessionManager()), deploymentContext, facade);
if (handler.handleRequest()) { if (handler.handleRequest()) {
return Authentication.SEND_SUCCESS; return Authentication.SEND_SUCCESS;
} }
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -1,40 +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.jetty;
import org.eclipse.jetty.server.SessionManager;
import org.keycloak.adapters.jetty.spi.JettySessionManager;
import javax.servlet.http.HttpSession;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class Jetty81SessionManager implements JettySessionManager {
protected SessionManager sessionManager;
public Jetty81SessionManager(SessionManager sessionManager) {
this.sessionManager = sessionManager;
}
@Override
public HttpSession getHttpSession(String id) {
return sessionManager.getHttpSession(id);
}
}
@@ -25,7 +25,6 @@ import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator; import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator;
import org.keycloak.adapters.jetty.core.JettySessionTokenStore; import org.keycloak.adapters.jetty.core.JettySessionTokenStore;
import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
import javax.servlet.ServletRequest; import javax.servlet.ServletRequest;
@@ -45,11 +44,6 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request)); return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request));
} }
@Override
public JettyUserSessionManagement createSessionManagement(Request request) {
return new JettyUserSessionManagement(new Jetty81SessionManager(request.getSessionManager()));
}
@Override @Override
protected Request resolveRequest(ServletRequest req) { protected Request resolveRequest(ServletRequest req) {
return (req instanceof Request)?(Request)req: AbstractHttpConnection.getCurrentConnection().getRequest(); return (req instanceof Request)?(Request)req: AbstractHttpConnection.getCurrentConnection().getRequest();
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -1,40 +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.jetty;
import org.eclipse.jetty.server.SessionManager;
import org.keycloak.adapters.jetty.spi.JettySessionManager;
import javax.servlet.http.HttpSession;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class Jetty91SessionManager implements JettySessionManager {
protected SessionManager sessionManager;
public Jetty91SessionManager(SessionManager sessionManager) {
this.sessionManager = sessionManager;
}
@Override
public HttpSession getHttpSession(String id) {
return sessionManager.getHttpSession(id);
}
}
@@ -27,7 +27,6 @@ import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator;
import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator; import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator;
import org.keycloak.adapters.jetty.core.JettySessionTokenStore; import org.keycloak.adapters.jetty.core.JettySessionTokenStore;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade; import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
import javax.servlet.ServletRequest; import javax.servlet.ServletRequest;
@@ -47,11 +46,6 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request)); return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request));
} }
@Override
public JettyUserSessionManagement createSessionManagement(Request request) {
return new JettyUserSessionManagement(new Jetty91SessionManager(request.getSessionManager()));
}
@Override @Override
protected Request resolveRequest(ServletRequest req) { protected Request resolveRequest(ServletRequest req) {
return (req instanceof Request) ? (Request)req : HttpChannel.getCurrentHttpChannel().getRequest(); return (req instanceof Request) ? (Request)req : HttpChannel.getCurrentHttpChannel().getRequest();
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -1,40 +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.jetty;
import org.eclipse.jetty.server.SessionManager;
import org.keycloak.adapters.jetty.spi.JettySessionManager;
import javax.servlet.http.HttpSession;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class Jetty92SessionManager implements JettySessionManager {
protected SessionManager sessionManager;
public Jetty92SessionManager(SessionManager sessionManager) {
this.sessionManager = sessionManager;
}
@Override
public HttpSession getHttpSession(String id) {
return sessionManager.getHttpSession(id);
}
}
@@ -27,7 +27,6 @@ import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator;
import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator; import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator;
import org.keycloak.adapters.jetty.core.JettySessionTokenStore; import org.keycloak.adapters.jetty.core.JettySessionTokenStore;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade; import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
import javax.servlet.ServletRequest; import javax.servlet.ServletRequest;
@@ -62,11 +61,6 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request)); return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request));
} }
@Override
public JettyUserSessionManagement createSessionManagement(Request request) {
return new JettyUserSessionManagement(new Jetty92SessionManager(request.getSessionManager()));
}
@Override @Override
protected JettyRequestAuthenticator createRequestAuthenticator(Request request, JettyHttpFacade facade, protected JettyRequestAuthenticator createRequestAuthenticator(Request request, JettyHttpFacade facade,
KeycloakDeployment deployment, AdapterTokenStore tokenStore) { KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -1,40 +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.jetty;
import org.eclipse.jetty.server.SessionManager;
import org.keycloak.adapters.jetty.spi.JettySessionManager;
import javax.servlet.http.HttpSession;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class Jetty93SessionManager implements JettySessionManager {
protected SessionManager sessionManager;
public Jetty93SessionManager(SessionManager sessionManager) {
this.sessionManager = sessionManager;
}
@Override
public HttpSession getHttpSession(String id) {
return sessionManager.getHttpSession(id);
}
}
@@ -26,7 +26,6 @@ import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator;
import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator; import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator;
import org.keycloak.adapters.jetty.core.JettySessionTokenStore; import org.keycloak.adapters.jetty.core.JettySessionTokenStore;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade; import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
import javax.servlet.ServletRequest; import javax.servlet.ServletRequest;
@@ -61,11 +60,6 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request)); return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request));
} }
@Override
public JettyUserSessionManagement createSessionManagement(Request request) {
return new JettyUserSessionManagement(new Jetty93SessionManager(request.getSessionManager()));
}
@Override @Override
protected JettyRequestAuthenticator createRequestAuthenticator(Request request, JettyHttpFacade facade, protected JettyRequestAuthenticator createRequestAuthenticator(Request request, JettyHttpFacade facade,
KeycloakDeployment deployment, AdapterTokenStore tokenStore) { KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
-156
View File
@@ -1,156 +0,0 @@
<?xml version="1.0"?>
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.0.Final</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-jetty94-adapter</artifactId>
<name>Keycloak Jetty 9.4.x Integration</name>
<properties>
<jetty9.version>9.4.2.v20170220</jetty9.version>
<keycloak.osgi.export>
org.keycloak.adapters.jetty.*
</keycloak.osgi.export>
<keycloak.osgi.import>
org.eclipse.jetty.*;resolution:=optional,
javax.servlet.*;version="[3.0,4)";resolution:=optional,
org.keycloak.*;version="${project.version}",
*;resolution:=optional
</keycloak.osgi.import>
</properties>
<description />
<dependencies>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>${jboss.logging.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-jetty-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.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty9.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes</phase>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
<configuration>
<instructions>
<Bundle-ClassPath>.</Bundle-ClassPath>
<Bundle-Name>${project.name}</Bundle-Name>
<Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
<Import-Package>${keycloak.osgi.import}</Import-Package>
<Export-Package>${keycloak.osgi.export}</Export-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -1,47 +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.jetty;
import org.eclipse.jetty.server.Request;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator;
import org.keycloak.adapters.spi.HttpFacade;
import javax.servlet.http.HttpSession;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class Jetty94RequestAuthenticator extends JettyRequestAuthenticator {
public Jetty94RequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, AdapterTokenStore tokenStore, int sslRedirectPort, Request request) {
super(facade, deployment, tokenStore, sslRedirectPort, request);
}
@Override
protected String changeHttpSessionId(boolean create) {
Request request = this.request;
HttpSession session = request.getSession(false);
if (session == null) {
return request.getSession(true).getId();
}
if (!deployment.isTurnOffChangeSessionIdOnLogin()) return request.changeSessionId();
else return session.getId();
}
}
@@ -1,40 +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.jetty;
import org.eclipse.jetty.server.session.SessionHandler;
import org.keycloak.adapters.jetty.spi.JettySessionManager;
import javax.servlet.http.HttpSession;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class Jetty94SessionManager implements JettySessionManager {
protected SessionHandler sessionHandler;
public Jetty94SessionManager(SessionHandler sessionHandler) {
this.sessionHandler = sessionHandler;
}
@Override
public HttpSession getHttpSession(String id) {
return sessionHandler.getHttpSession(id);
}
}
@@ -1,111 +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.jetty;
import org.eclipse.jetty.security.authentication.FormAuthenticator;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.MultiMap;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import org.keycloak.adapters.spi.AdapterSessionStore;
import org.keycloak.common.util.MultivaluedHashMap;
import javax.servlet.http.HttpSession;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class JettyAdapterSessionStore implements AdapterSessionStore {
public static final String CACHED_FORM_PARAMETERS = "__CACHED_FORM_PARAMETERS";
protected Request myRequest;
public JettyAdapterSessionStore(Request request) {
this.myRequest = request; // for IDE/compilation purposes
}
protected MultiMap<String> extractFormParameters(Request base_request) {
MultiMap<String> formParameters = new MultiMap<String>();
base_request.extractFormParameters(formParameters);
return formParameters;
}
protected void restoreFormParameters(MultiMap<String> j_post, Request base_request) {
base_request.setContentParameters(j_post);
}
public boolean restoreRequest() {
HttpSession session = myRequest.getSession(false);
if (session == null) return false;
synchronized (session) {
String j_uri = (String) session.getAttribute(FormAuthenticator.__J_URI);
if (j_uri != null) {
// check if the request is for the same url as the original and restore
// params if it was a post
StringBuffer buf = myRequest.getRequestURL();
if (myRequest.getQueryString() != null)
buf.append("?").append(myRequest.getQueryString());
if (j_uri.equals(buf.toString())) {
String method = (String)session.getAttribute(JettyHttpFacade.__J_METHOD);
myRequest.setMethod(method);
MultivaluedHashMap<String, String> j_post = (MultivaluedHashMap<String, String>) session.getAttribute(CACHED_FORM_PARAMETERS);
if (j_post != null) {
myRequest.setContentType("application/x-www-form-urlencoded");
MultiMap<String> map = new MultiMap<String>();
for (String key : j_post.keySet()) {
for (String val : j_post.getList(key)) {
map.add(key, val);
}
}
restoreFormParameters(map, myRequest);
}
session.removeAttribute(FormAuthenticator.__J_URI);
session.removeAttribute(JettyHttpFacade.__J_METHOD);
session.removeAttribute(FormAuthenticator.__J_POST);
}
return true;
}
}
return false;
}
public void saveRequest() {
// remember the current URI
HttpSession session = myRequest.getSession();
synchronized (session) {
// But only if it is not set already, or we save every uri that leads to a login form redirect
if (session.getAttribute(FormAuthenticator.__J_URI) == null) {
StringBuffer buf = myRequest.getRequestURL();
if (myRequest.getQueryString() != null)
buf.append("?").append(myRequest.getQueryString());
session.setAttribute(FormAuthenticator.__J_URI, buf.toString());
session.setAttribute(JettyHttpFacade.__J_METHOD, myRequest.getMethod());
if ("application/x-www-form-urlencoded".equals(myRequest.getContentType()) && "POST".equalsIgnoreCase(myRequest.getMethod())) {
MultiMap<String> formParameters = extractFormParameters(myRequest);
MultivaluedHashMap<String, String> map = new MultivaluedHashMap<String, String>();
for (String key : formParameters.keySet()) {
for (Object value : formParameters.getValues(key)) {
map.add(key, (String) value);
}
}
session.setAttribute(CACHED_FORM_PARAMETERS, map);
}
}
}
}
}
@@ -1,75 +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.jetty;
import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.UserIdentity;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator;
import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator;
import org.keycloak.adapters.jetty.core.JettySessionTokenStore;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
import javax.servlet.ServletRequest;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticator {
public KeycloakJettyAuthenticator() {
super();
}
@Override
protected Request resolveRequest(ServletRequest req) {
return Request.getBaseRequest(req);
}
@Override
protected Authentication createAuthentication(UserIdentity userIdentity, final Request request) {
return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
@Override
public void logout() {
logoutCurrent(request);
}
};
}
@Override
public AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment) {
return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request));
}
@Override
public JettyUserSessionManagement createSessionManagement(Request request) {
return new JettyUserSessionManagement(new Jetty94SessionManager(request.getSessionHandler()));
}
@Override
protected JettyRequestAuthenticator createRequestAuthenticator(Request request, JettyHttpFacade facade,
KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
return new Jetty94RequestAuthenticator(facade, deployment, tokenStore, -1, request);
}
}
+2 -16
View File
@@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<name>Keycloak Jetty Integration</name> <name>Keycloak Jetty Integration</name>
@@ -33,22 +33,8 @@
<modules> <modules>
<module>jetty-core</module> <module>jetty-core</module>
<module>jetty8.1</module> <module>jetty8.1</module>
<module>jetty9.1</module>
<module>jetty9.2</module> <module>jetty9.2</module>
<module>jetty9.3</module> <module>jetty9.3</module>
</modules> </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> </project>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <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>;
}
}
+65 -69
View File
@@ -87,10 +87,6 @@
} }
kc.flow = initOptions.flow; kc.flow = initOptions.flow;
} }
if (initOptions.timeSkew != null) {
kc.timeSkew = initOptions.timeSkew;
}
} }
if (!kc.responseMode) { if (!kc.responseMode) {
@@ -157,7 +153,7 @@
processCallback(callback, initPromise); processCallback(callback, initPromise);
return; return;
} else if (initOptions) { } else if (initOptions) {
if (initOptions.token && initOptions.refreshToken) { if (initOptions.token || initOptions.refreshToken) {
setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken); setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken);
if (loginIframe.enable) { if (loginIframe.enable) {
@@ -166,8 +162,12 @@
kc.onAuthSuccess && kc.onAuthSuccess(); kc.onAuthSuccess && kc.onAuthSuccess();
initPromise.setSuccess(); initPromise.setSuccess();
}).error(function () { }).error(function () {
setToken(null, null, null); kc.onAuthError && kc.onAuthError();
initPromise.setSuccess(); if (initOptions.onLoad) {
onLoad();
} else {
initPromise.setError();
}
}); });
}); });
} else { } else {
@@ -369,11 +369,6 @@
throw 'Not authenticated'; throw 'Not authenticated';
} }
if (kc.timeSkew == null) {
console.info('[KEYCLOAK] Unable to determine if token is expired as timeskew is not set');
return true;
}
var expiresIn = kc.tokenParsed['exp'] - Math.ceil(new Date().getTime() / 1000) + kc.timeSkew; var expiresIn = kc.tokenParsed['exp'] - Math.ceil(new Date().getTime() / 1000) + kc.timeSkew;
if (minValidity) { if (minValidity) {
expiresIn -= minValidity; expiresIn -= minValidity;
@@ -384,7 +379,7 @@
kc.updateToken = function(minValidity) { kc.updateToken = function(minValidity) {
var promise = createPromise(); var promise = createPromise();
if (!kc.refreshToken) { if (!kc.tokenParsed || !kc.refreshToken) {
promise.setError(); promise.setError();
return promise.promise; return promise.promise;
} }
@@ -393,10 +388,14 @@
var exec = function() { var exec = function() {
var refreshToken = false; var refreshToken = false;
if (minValidity == -1) { if (kc.timeSkew == -1) {
console.info('Skew ' + kc.timeSkew);
refreshToken = true;
console.info('[KEYCLOAK] Refreshing token: time skew not set');
} else if (minValidity == -1) {
refreshToken = true; refreshToken = true;
console.info('[KEYCLOAK] Refreshing token: forced refresh'); console.info('[KEYCLOAK] Refreshing token: forced refresh');
} else if (!kc.tokenParsed || kc.isTokenExpired(minValidity)) { } else if (kc.isTokenExpired(minValidity)) {
refreshToken = true; refreshToken = true;
console.info('[KEYCLOAK] Refreshing token: token expired'); console.info('[KEYCLOAK] Refreshing token: token expired');
} }
@@ -587,7 +586,7 @@
req.onreadystatechange = function () { req.onreadystatechange = function () {
if (req.readyState == 4) { if (req.readyState == 4) {
if (req.status == 200 || fileLoaded(req)) { if (req.status == 200) {
var config = JSON.parse(req.responseText); var config = JSON.parse(req.responseText);
kc.authServerUrl = config['auth-server-url']; kc.authServerUrl = config['auth-server-url'];
@@ -633,16 +632,55 @@
return promise.promise; return promise.promise;
} }
function fileLoaded(xhr) {
return xhr.status == 0 && xhr.responseText && xhr.responseURL.startsWith('file:');
}
function setToken(token, refreshToken, idToken, timeLocal) { function setToken(token, refreshToken, idToken, timeLocal) {
if (kc.tokenTimeoutHandle) { if (kc.tokenTimeoutHandle) {
clearTimeout(kc.tokenTimeoutHandle); clearTimeout(kc.tokenTimeoutHandle);
kc.tokenTimeoutHandle = null; kc.tokenTimeoutHandle = null;
} }
if (token) {
kc.token = token;
kc.tokenParsed = decodeToken(token);
var sessionId = kc.realm + '/' + kc.tokenParsed.sub;
if (kc.tokenParsed.session_state) {
sessionId = sessionId + '/' + kc.tokenParsed.session_state;
}
kc.sessionId = sessionId;
kc.authenticated = true;
kc.subject = kc.tokenParsed.sub;
kc.realmAccess = kc.tokenParsed.realm_access;
kc.resourceAccess = kc.tokenParsed.resource_access;
if (timeLocal) {
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
console.info('[KEYCLOAK] Estimated time difference between browser and server is ' + kc.timeSkew + ' seconds');
} else {
kc.timeSkew = -1;
}
if (kc.onTokenExpired) {
if (kc.timeSkew == -1) {
kc.onTokenExpired();
} else {
var expiresIn = (kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew) * 1000;
if (expiresIn <= 0) {
kc.onTokenExpired();
} else {
kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn);
}
}
}
} else {
delete kc.token;
delete kc.tokenParsed;
delete kc.subject;
delete kc.realmAccess;
delete kc.resourceAccess;
kc.authenticated = false;
}
if (refreshToken) { if (refreshToken) {
kc.refreshToken = refreshToken; kc.refreshToken = refreshToken;
kc.refreshTokenParsed = decodeToken(refreshToken); kc.refreshTokenParsed = decodeToken(refreshToken);
@@ -658,42 +696,6 @@
delete kc.idToken; delete kc.idToken;
delete kc.idTokenParsed; delete kc.idTokenParsed;
} }
if (token) {
kc.token = token;
kc.tokenParsed = decodeToken(token);
kc.sessionId = kc.tokenParsed.session_state;
kc.authenticated = true;
kc.subject = kc.tokenParsed.sub;
kc.realmAccess = kc.tokenParsed.realm_access;
kc.resourceAccess = kc.tokenParsed.resource_access;
if (timeLocal) {
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
}
if (kc.timeSkew != null) {
console.info('[KEYCLOAK] Estimated time difference between browser and server is ' + kc.timeSkew + ' seconds');
if (kc.onTokenExpired) {
var expiresIn = (kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew) * 1000;
console.info('[KEYCLOAK] Token expires in ' + Math.round(expiresIn / 1000) + ' s');
if (expiresIn <= 0) {
kc.onTokenExpired();
} else {
kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn);
}
}
}
} else {
delete kc.token;
delete kc.tokenParsed;
delete kc.subject;
delete kc.realmAccess;
delete kc.resourceAccess;
kc.authenticated = false;
}
} }
function decodeToken(str) { function decodeToken(str) {
@@ -836,28 +838,22 @@
document.body.appendChild(iframe); document.body.appendChild(iframe);
var messageCallback = function(event) { var messageCallback = function(event) {
if ((event.origin !== loginIframe.iframeOrigin) || (loginIframe.iframe.contentWindow !== event.source)) { if (event.origin !== loginIframe.iframeOrigin) {
return; return;
} }
if (!(event.data == 'unchanged' || event.data == 'changed' || event.data == 'error')) { if (event.data != "unchanged") {
return;
}
if (event.data != 'unchanged') {
kc.clearToken(); kc.clearToken();
} }
var callbacks = loginIframe.callbackList.splice(0, loginIframe.callbackList.length); for (var i = loginIframe.callbackList.length - 1; i >= 0; --i) {
var promise = loginIframe.callbackList[i];
for (var i = callbacks.length - 1; i >= 0; --i) { if (event.data == "unchanged") {
var promise = callbacks[i];
if (event.data == 'unchanged') {
promise.setSuccess(); promise.setSuccess();
} else { } else {
promise.setError(); promise.setError();
} }
loginIframe.callbackList.splice(i, 1);
} }
}; };
@@ -1232,7 +1228,7 @@
break; break;
default: default:
if (responseMode != 'query' || !handleQueryParam(param, queryParams[param], oauth)) { 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; break;
} }
@@ -35,14 +35,14 @@
req.open('GET', url, true); req.open('GET', url, true);
req.onreadystatechange = function () { req.onreadystatechange = function () {
if (req.readyState === 4) { if (req.readyState == 4) {
if (req.status === 204) { if (req.status == 204) {
init = { init = {
clientId: clientId, clientId: clientId,
origin: origin origin: origin
} }
callback('unchanged'); callback('unchanged');
} else if (req.status === 404) { } else if (req.status = 404) {
callback('changed'); callback('changed');
} else { } else {
callback('error'); callback('error');
@@ -52,9 +52,8 @@
req.send(); req.send();
} else { } else {
if (clientId === init.clientId && origin === init.origin) { if (clientId == init.clientId && origin == init.origin) {
var c = cookie.split('/'); if (sessionState == cookie) {
if (sessionState === c[2]) {
callback('unchanged'); callback('unchanged');
} else { } else {
callback('changed'); callback('changed');
@@ -72,7 +71,7 @@
for(var i=0; i<ca.length; i++) for(var i=0; i<ca.length; i++)
{ {
var c = ca[i].trim(); var c = ca[i].trim();
if (c.indexOf(name)===0) return c.substring(name.length,c.length); if (c.indexOf(name)==0) return c.substring(name.length,c.length);
} }
return null; return null;
} }
@@ -82,7 +81,7 @@
var origin = event.origin; var origin = event.origin;
var data = event.data.split(' '); var data = event.data.split(' ');
if (data.length != 2) { if (data.length != 2) {
return; event.source.postMessage('error', origin);
} }
var clientId = data[0]; var clientId = data[0];
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -1,83 +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.osgi;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.spi.HttpFacade;
import org.osgi.framework.BundleContext;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class BundleBasedKeycloakConfigResolver implements KeycloakConfigResolver {
private volatile KeycloakDeployment cachedDeployment;
private BundleContext bundleContext;
private String configLocation = "WEB-INF/keycloak.json";
public BundleContext getBundleContext() {
return bundleContext;
}
public void setBundleContext(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
public String getConfigLocation() {
return configLocation;
}
public void setConfigLocation(String configLocation) {
this.configLocation = configLocation;
}
@Override
public KeycloakDeployment resolve(HttpFacade.Request request) {
if (cachedDeployment != null) {
return cachedDeployment;
} else {
cachedDeployment = findDeployment(request);
return cachedDeployment;
}
}
protected KeycloakDeployment findDeployment(HttpFacade.Request request) {
if (bundleContext == null) {
throw new IllegalStateException("bundleContext must be set for BundleBasedKeycloakConfigResolver!");
}
URL url = bundleContext.getBundle().getResource(configLocation);
if (url == null) {
throw new IllegalStateException("Failed to find the file " + configLocation + " on classpath.");
}
try {
InputStream is = url.openStream();
return KeycloakDeploymentBuilder.build(is);
} catch (IOException ioe) {
throw new IllegalStateException("Error reading file' " + configLocation + "' from bundle classpath.", ioe);
}
}
}
+1 -3
View File
@@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<name>Keycloak OIDC Client Adapter Modules</name> <name>Keycloak OIDC Client Adapter Modules</name>
@@ -41,11 +41,9 @@
<module>servlet-filter</module> <module>servlet-filter</module>
<module>servlet-oauth-client</module> <module>servlet-oauth-client</module>
<module>spring-boot</module> <module>spring-boot</module>
<module>spring-boot-container-bundle</module>
<module>spring-security</module> <module>spring-security</module>
<module>tomcat</module> <module>tomcat</module>
<module>undertow</module> <module>undertow</module>
<module>wildfly</module> <module>wildfly</module>
<module>wildfly-elytron</module>
</modules> </modules>
</project> </project>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -53,52 +53,22 @@ import java.util.regex.Pattern;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class KeycloakOIDCFilter implements Filter { 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 AdapterDeploymentContext deploymentContext;
protected SessionIdMapper idMapper = new InMemorySessionIdMapper(); protected SessionIdMapper idMapper = new InMemorySessionIdMapper();
protected NodesRegistrationManagement nodesRegistrationManagement; protected NodesRegistrationManagement nodesRegistrationManagement;
protected Pattern skipPattern; protected Pattern skipPattern;
private final KeycloakConfigResolver definedconfigResolver; private final static Logger log = Logger.getLogger(""+KeycloakOIDCFilter.class);
/**
* 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);
}
@Override @Override
public void init(final FilterConfig filterConfig) throws ServletException { public void init(final FilterConfig filterConfig) throws ServletException {
String skipPatternDefinition = filterConfig.getInitParameter(SKIP_PATTERN_PARAM);
String skipPatternDefinition = filterConfig.getInitParameter("keycloak.config.skipPattern");
if (skipPatternDefinition != null) { if (skipPatternDefinition != null) {
skipPattern = Pattern.compile(skipPatternDefinition, Pattern.DOTALL); skipPattern = Pattern.compile(skipPatternDefinition, Pattern.DOTALL);
} }
if (definedconfigResolver != null) { String configResolverClass = filterConfig.getInitParameter("keycloak.config.resolver");
deploymentContext = new AdapterDeploymentContext(definedconfigResolver);
log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", definedconfigResolver.getClass());
} else {
String configResolverClass = filterConfig.getInitParameter(CONFIG_RESOLVER_PARAM);
if (configResolverClass != null) { if (configResolverClass != null) {
try { try {
KeycloakConfigResolver configResolver = (KeycloakConfigResolver) getClass().getClassLoader().loadClass(configResolverClass).newInstance(); KeycloakConfigResolver configResolver = (KeycloakConfigResolver) getClass().getClassLoader().loadClass(configResolverClass).newInstance();
@@ -109,7 +79,7 @@ public class KeycloakOIDCFilter implements Filter {
deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment()); deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
} }
} else { } else {
String fp = filterConfig.getInitParameter(CONFIG_FILE_PARAM); String fp = filterConfig.getInitParameter("keycloak.config.file");
InputStream is = null; InputStream is = null;
if (fp != null) { if (fp != null) {
try { try {
@@ -119,7 +89,7 @@ public class KeycloakOIDCFilter implements Filter {
} }
} else { } else {
String path = "/WEB-INF/keycloak.json"; String path = "/WEB-INF/keycloak.json";
String pathParam = filterConfig.getInitParameter(CONFIG_PATH_PARAM); String pathParam = filterConfig.getInitParameter("keycloak.config.path");
if (pathParam != null) path = pathParam; if (pathParam != null) path = pathParam;
is = filterConfig.getServletContext().getResourceAsStream(path); is = filterConfig.getServletContext().getResourceAsStream(path);
} }
@@ -127,23 +97,26 @@ public class KeycloakOIDCFilter implements Filter {
deploymentContext = new AdapterDeploymentContext(kd); deploymentContext = new AdapterDeploymentContext(kd);
log.fine("Keycloak is using a per-deployment configuration."); log.fine("Keycloak is using a per-deployment configuration.");
} }
}
filterConfig.getServletContext().setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext); filterConfig.getServletContext().setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
nodesRegistrationManagement = new NodesRegistrationManagement(); nodesRegistrationManagement = new NodesRegistrationManagement();
} }
private KeycloakDeployment createKeycloakDeploymentFrom(InputStream is) { private KeycloakDeployment createKeycloakDeploymentFrom(InputStream is) {
if (is == null) { if (is == null) {
log.fine("No adapter configuration. Keycloak is unconfigured and will deny all requests."); log.fine("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
return new KeycloakDeployment(); return new KeycloakDeployment();
} }
return KeycloakDeploymentBuilder.build(is); return KeycloakDeploymentBuilder.build(is);
} }
@Override @Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
log.fine("Keycloak OIDC Filter"); log.fine("Keycloak OIDC Filter");
//System.err.println("Keycloak OIDC Filter: " + ((HttpServletRequest)req).getRequestURL().toString());
HttpServletRequest request = (HttpServletRequest) req; HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res; HttpServletResponse response = (HttpServletResponse) res;
@@ -168,7 +168,7 @@ public class OIDCFilterSessionStore extends FilterSessionStore implements Adapte
HttpSession httpSession = request.getSession(); HttpSession httpSession = request.getSession();
httpSession.setAttribute(KeycloakAccount.class.getName(), sAccount); httpSession.setAttribute(KeycloakAccount.class.getName(), sAccount);
httpSession.setAttribute(KeycloakSecurityContext.class.getName(), sAccount.getKeycloakSecurityContext()); 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(); //String username = securityContext.getToken().getSubject();
//log.fine("userSessionManagement.login: " + username); //log.fine("userSessionManagement.login: " + username);
} }
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -41,60 +41,12 @@ import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.util.List; 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> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient { 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 * closes client
*/ */
@@ -105,16 +57,8 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
private AccessTokenResponse resolveBearerToken(HttpServletRequest request, String redirectUri, String code) throws IOException, ServerRequest.HttpFailure { private AccessTokenResponse resolveBearerToken(HttpServletRequest request, String redirectUri, String code) throws IOException, ServerRequest.HttpFailure {
// Don't send sessionId in oauth clients for now // Don't send sessionId in oauth clients for now
KeycloakDeployment resolvedDeployment = resolveDeployment(getDeployment(), request); 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);
} }
}
/** /**
* Start the process of obtaining an access token by redirecting the browser * Start the process of obtaining an access token by redirecting the browser
@@ -150,12 +94,6 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
String authUrl = resolvedDeployment.getAuthUrl().clone().build().toString(); String authUrl = resolvedDeployment.getAuthUrl().clone().build().toString();
String scopeParam = TokenUtil.attachOIDCScope(scope); String scopeParam = TokenUtil.attachOIDCScope(scope);
// https://tools.ietf.org/html/rfc7636#section-4
if (resolvedDeployment.isPkce()) {
setCodeVerifier();
setCodeChallenge();
}
KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(authUrl) KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(authUrl)
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE) .queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
.queryParam(OAuth2Constants.CLIENT_ID, getClientId()) .queryParam(OAuth2Constants.CLIENT_ID, getClientId())
@@ -280,11 +218,6 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
return servletRequest.getRequestURL().toString(); return servletRequest.getRequestURL().toString();
} }
@Override
public String getRelativePath() {
return servletRequest.getServletPath();
}
@Override @Override
public boolean isSecure() { public boolean isSecure() {
return servletRequest.isSecure(); return servletRequest.isSecure();
@@ -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.0.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 -36
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -31,7 +31,7 @@
<description/> <description/>
<properties> <properties>
<spring-boot.version>1.3.0.RELEASE</spring-boot.version> <spring-boot.version>1.2.1.RELEASE</spring-boot.version>
</properties> </properties>
<dependencies> <dependencies>
@@ -43,67 +43,74 @@
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId> <artifactId>keycloak-core</artifactId>
</dependency> </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>
<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> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version> <version>${spring-boot.version}</version>
<scope>compile</scope> <scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-servlet</artifactId>
<scope>compile</scope>
<optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId> <artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version> <version>${jetty9.version}</version>
<scope>compile</scope> <scope>provided</scope>
<optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId> <artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version> <version>${jetty9.version}</version>
<scope>compile</scope> <scope>provided</scope>
<optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId> <artifactId>jetty-webapp</artifactId>
<version>${jetty9.version}</version> <version>${jetty9.version}</version>
<scope>compile</scope> <scope>provided</scope>
<optional>true</optional>
</dependency> </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> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
<version>${spring-boot.version}</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
@@ -111,10 +118,11 @@
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<configuration> <configuration>
<source>1.7</source> <source>1.6</source>
<target>1.7</target> <target>1.6</target>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
</project> </project>
@@ -33,7 +33,6 @@ import org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve;
import org.keycloak.adapters.undertow.KeycloakServletExtension; import org.keycloak.adapters.undertow.KeycloakServletExtension;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 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.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
@@ -61,8 +60,7 @@ import java.util.Set;
@Configuration @Configuration
@ConditionalOnWebApplication @ConditionalOnWebApplication
@EnableConfigurationProperties(KeycloakSpringBootProperties.class) @EnableConfigurationProperties(KeycloakSpringBootProperties.class)
@ConditionalOnProperty(value = "keycloak.enabled", matchIfMissing = true) public class KeycloakSpringBootConfiguration {
public class KeycloakAutoConfiguration {
private KeycloakSpringBootProperties keycloakProperties; private KeycloakSpringBootProperties keycloakProperties;
@@ -72,7 +70,6 @@ public class KeycloakAutoConfiguration {
KeycloakSpringBootConfigResolver.setAdapterConfig(keycloakProperties); KeycloakSpringBootConfigResolver.setAdapterConfig(keycloakProperties);
} }
@Bean @Bean
public EmbeddedServletContainerCustomizer getKeycloakContainerCustomizer() { public EmbeddedServletContainerCustomizer getKeycloakContainerCustomizer() {
return new EmbeddedServletContainerCustomizer() { return new EmbeddedServletContainerCustomizer() {
@@ -144,11 +141,11 @@ public class KeycloakAutoConfiguration {
List<io.undertow.servlet.api.SecurityConstraint> undertowSecurityConstraints = new ArrayList<io.undertow.servlet.api.SecurityConstraint>(); List<io.undertow.servlet.api.SecurityConstraint> undertowSecurityConstraints = new ArrayList<io.undertow.servlet.api.SecurityConstraint>();
for (KeycloakSpringBootProperties.SecurityConstraint constraintDefinition : keycloakProperties.getSecurityConstraints()) { 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()) { 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 webResourceCollection = new WebResourceCollection();
webResourceCollection.addHttpMethods(collectionDefinition.getMethods()); webResourceCollection.addHttpMethods(collectionDefinition.getMethods());
webResourceCollection.addHttpMethodOmissions(collectionDefinition.getOmittedMethods()); webResourceCollection.addHttpMethodOmissions(collectionDefinition.getOmittedMethods());
@@ -156,10 +153,9 @@ public class KeycloakAutoConfiguration {
undertowSecurityConstraint.addWebResourceCollections(webResourceCollection); undertowSecurityConstraint.addWebResourceCollections(webResourceCollection);
}
undertowSecurityConstraints.add(undertowSecurityConstraint); undertowSecurityConstraints.add(undertowSecurityConstraint);
} }
}
return undertowSecurityConstraints; return undertowSecurityConstraints;
} }
} }
@@ -178,70 +174,42 @@ public class KeycloakAutoConfiguration {
KeycloakJettyAuthenticator keycloakJettyAuthenticator = new KeycloakJettyAuthenticator(); KeycloakJettyAuthenticator keycloakJettyAuthenticator = new KeycloakJettyAuthenticator();
keycloakJettyAuthenticator.setConfigResolver(new KeycloakSpringBootConfigResolver()); 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>(); List<ConstraintMapping> jettyConstraintMappings = new ArrayList<ConstraintMapping>();
for (KeycloakSpringBootProperties.SecurityConstraint constraintDefinition : keycloakProperties.getSecurityConstraints()) { for (KeycloakSpringBootProperties.SecurityConstraint constraintDefinition : keycloakProperties.getSecurityConstraints()) {
for (KeycloakSpringBootProperties.SecurityCollection securityCollectionDefinition : constraintDefinition for (KeycloakSpringBootProperties.SecurityCollection securityCollectionDefinition : constraintDefinition
.getSecurityCollections()) { .getSecurityCollections()) {
// securityCollection matches servlet spec's web-resource-collection
Constraint jettyConstraint = new Constraint();
if (constraintDefinition.getAuthRoles().size() > 0) { Constraint jettyConstraint = new Constraint();
jettyConstraint.setName(securityCollectionDefinition.getName());
jettyConstraint.setAuthenticate(true); jettyConstraint.setAuthenticate(true);
jettyConstraint.setRoles(constraintDefinition.getAuthRoles().toArray(new String[0]));
if (securityCollectionDefinition.getName() != null) {
jettyConstraint.setName(securityCollectionDefinition.getName());
} }
jettyConstraint.setName(securityCollectionDefinition.getName()); jettyConstraint.setRoles(securityCollectionDefinition.getAuthRoles().toArray(new String[0]));
// according to the servlet spec each security-constraint has at least one URL pattern ConstraintMapping jettyConstraintMapping = new ConstraintMapping();
for(String pattern : securityCollectionDefinition.getPatterns()) { if (securityCollectionDefinition.getPatterns().size() > 0) {
//First pattern wins
/* the following code is asymmetric as Jetty's ConstraintMapping accepts only one allowed HTTP method, jettyConstraintMapping.setPathSpec(securityCollectionDefinition.getPatterns().get(0));
but multiple omitted methods. Therefore we add one ConstraintMapping for each allowed jettyConstraintMapping.setConstraint(jettyConstraint);
mapping but only one mapping in the cases of omitted methods or no methods. }
*/
if (securityCollectionDefinition.getMethods().size() > 0) { if (securityCollectionDefinition.getMethods().size() > 0) {
// according to the servlet spec we have either methods ... //First method wins
for(String method : securityCollectionDefinition.getMethods()) { jettyConstraintMapping.setMethod(securityCollectionDefinition.getMethods().get(0));
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( jettyConstraintMapping.setMethodOmissions(
securityCollectionDefinition.getOmittedMethods().toArray(new String[0])); securityCollectionDefinition.getOmittedMethods().toArray(new String[0]));
} else {
// ... or no methods at all
ConstraintMapping jettyConstraintMapping = new ConstraintMapping();
jettyConstraintMappings.add(jettyConstraintMapping); jettyConstraintMappings.add(jettyConstraintMapping);
jettyConstraintMapping.setConstraint(jettyConstraint);
jettyConstraintMapping.setPathSpec(pattern);
}
}
} }
} }
WebAppContext webAppContext = server.getBean(WebAppContext.class); 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(); ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
securityHandler.setConstraintMappings(jettyConstraintMappings); securityHandler.setConstraintMappings(jettyConstraintMappings);
@@ -267,21 +235,19 @@ public class KeycloakAutoConfiguration {
Set<String> authRoles = new HashSet<String>(); Set<String> authRoles = new HashSet<String>();
for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) { for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) {
for (String authRole : constraint.getAuthRoles()) { for (KeycloakSpringBootProperties.SecurityCollection collection : constraint.getSecurityCollections()) {
for (String authRole : collection.getAuthRoles()) {
if (!authRoles.contains(authRole)) { if (!authRoles.contains(authRole)) {
context.addSecurityRole(authRole); context.addSecurityRole(authRole);
authRoles.add(authRole); authRoles.add(authRole);
} }
} }
} }
}
for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) { for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) {
SecurityConstraint tomcatConstraint = new SecurityConstraint(); SecurityConstraint tomcatConstraint = new SecurityConstraint();
for (String authRole : constraint.getAuthRoles()) {
tomcatConstraint.addAuthRole(authRole);
}
for (KeycloakSpringBootProperties.SecurityCollection collection : constraint.getSecurityCollections()) { for (KeycloakSpringBootProperties.SecurityCollection collection : constraint.getSecurityCollections()) {
SecurityCollection tomcatSecCollection = new SecurityCollection(); SecurityCollection tomcatSecCollection = new SecurityCollection();
@@ -292,6 +258,10 @@ public class KeycloakAutoConfiguration {
tomcatSecCollection.setDescription(collection.getDescription()); tomcatSecCollection.setDescription(collection.getDescription());
} }
for (String authRole : collection.getAuthRoles()) {
tomcatConstraint.addAuthRole(authRole);
}
for (String pattern : collection.getPatterns()) { for (String pattern : collection.getPatterns()) {
tomcatSecCollection.addPattern(pattern); tomcatSecCollection.addPattern(pattern);
} }
@@ -34,42 +34,14 @@ public class KeycloakSpringBootProperties extends AdapterConfig {
@JsonIgnore @JsonIgnore
private Map config = new HashMap(); private Map config = new HashMap();
/**
* Allow enabling of Keycloak Spring Boot adapter by configuration.
*/
private boolean enabled = true;
public Map getConfig() { public Map getConfig() {
return config; return config;
} }
/**
* To provide Java EE security constraints
*/
private List<SecurityConstraint> securityConstraints = new ArrayList<SecurityConstraint>(); 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 { public static class SecurityConstraint {
/**
* A list of security collections
*/
private List<SecurityCollection> securityCollections = new ArrayList<SecurityCollection>(); private List<SecurityCollection> securityCollections = new ArrayList<SecurityCollection>();
private List<String> authRoles = new ArrayList<String>();
public List<String> getAuthRoles() {
return authRoles;
}
public List<SecurityCollection> getSecurityCollections() { public List<SecurityCollection> getSecurityCollections() {
return securityCollections; return securityCollections;
@@ -78,39 +50,20 @@ public class KeycloakSpringBootProperties extends AdapterConfig {
public void setSecurityCollections(List<SecurityCollection> securityCollections) { public void setSecurityCollections(List<SecurityCollection> securityCollections) {
this.securityCollections = 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 { public static class SecurityCollection {
/**
* The name of your security constraint
*/
private String name; private String name;
/**
* The description of your security collection
*/
private String description; private String description;
/** private List<String> authRoles = new ArrayList<String>();
* A list of URL patterns that should match to apply the security collection
*/
private List<String> patterns = new ArrayList<String>(); private List<String> patterns = new ArrayList<String>();
/**
* A list of HTTP methods that applies for this security collection
*/
private List<String> methods = new ArrayList<String>(); private List<String> methods = new ArrayList<String>();
/**
* A list of HTTP methods that will be omitted for this security collection
*/
private List<String> omittedMethods = new ArrayList<String>(); private List<String> omittedMethods = new ArrayList<String>();
public List<String> getAuthRoles() {
return authRoles;
}
public List<String> getPatterns() { public List<String> getPatterns() {
return patterns; return patterns;
} }
@@ -139,6 +92,10 @@ public class KeycloakSpringBootProperties extends AdapterConfig {
this.description = description; this.description = description;
} }
public void setAuthRoles(List<String> authRoles) {
this.authRoles = authRoles;
}
public void setPatterns(List<String> patterns) { public void setPatterns(List<String> patterns) {
this.patterns = patterns; this.patterns = patterns;
} }
@@ -1,2 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.keycloak.adapters.springboot.KeycloakAutoConfiguration org.keycloak.adapters.springboot.KeycloakSpringBootConfiguration
+1 -3
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -59,14 +59,12 @@
<groupId>org.springframework.security</groupId> <groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId> <artifactId>spring-security-config</artifactId>
<version>${spring-security.version}</version> <version>${spring-security.version}</version>
<optional>true</optional>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.security</groupId> <groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId> <artifactId>spring-security-web</artifactId>
<version>${spring-security.version}</version> <version>${spring-security.version}</version>
<optional>true</optional>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<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 {
}
@@ -30,12 +30,6 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/** /**
* Provides a Keycloak {@link AuthenticationEntryPoint authentication entry point}. Uses a * Provides a Keycloak {@link AuthenticationEntryPoint authentication entry point}. Uses a
@@ -63,14 +57,11 @@ public class KeycloakAuthenticationEntryPoint implements AuthenticationEntryPoin
private String loginUri = DEFAULT_LOGIN_URI; private String loginUri = DEFAULT_LOGIN_URI;
private String realm = DEFAULT_REALM; private String realm = DEFAULT_REALM;
private AdapterDeploymentContext adapterDeploymentContext;
/** /**
* Creates a new Keycloak authentication entry point. * Creates a new Keycloak authentication entry point.
*/ */
public KeycloakAuthenticationEntryPoint(AdapterDeploymentContext adapterDeploymentContext) { public KeycloakAuthenticationEntryPoint() {
this(DEFAULT_API_REQUEST_MATCHER); this(DEFAULT_API_REQUEST_MATCHER);
this.adapterDeploymentContext = adapterDeploymentContext;
} }
/** /**
@@ -88,8 +79,7 @@ public class KeycloakAuthenticationEntryPoint implements AuthenticationEntryPoin
@Override @Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException
{ {
HttpFacade facade = new SimpleHttpFacade(request, response); if (apiRequestMatcher.matches(request)) {
if (apiRequestMatcher.matches(request) || adapterDeploymentContext.resolveDeployment(facade).isBearerOnly()) {
commenceUnauthorizedResponse(request, response); commenceUnauthorizedResponse(request, response);
} else { } else {
commenceLoginRedirect(request, response); commenceLoginRedirect(request, response);
@@ -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");
}
}
@@ -71,8 +71,8 @@ public abstract class KeycloakWebSecurityConfigurerAdapter extends WebSecurityCo
return factoryBean.getObject(); return factoryBean.getObject();
} }
protected AuthenticationEntryPoint authenticationEntryPoint() throws Exception { protected AuthenticationEntryPoint authenticationEntryPoint() {
return new KeycloakAuthenticationEntryPoint(adapterDeploymentContext()); return new KeycloakAuthenticationEntryPoint();
} }
protected KeycloakAuthenticationProvider keycloakAuthenticationProvider() { protected KeycloakAuthenticationProvider keycloakAuthenticationProvider() {
@@ -70,11 +70,6 @@ class WrappedHttpServletRequest implements Request {
return buf.toString(); return buf.toString();
} }
@Override
public String getRelativePath() {
return request.getServletPath();
}
@Override @Override
public boolean isSecure() { public boolean isSecure() {
return request.isSecure(); return request.isSecure();
@@ -1,90 +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.filter;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.AuthenticatedActionsHandler;
import org.keycloak.adapters.NodesRegistrationManagement;
import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.filter.GenericFilterBean;
/**
* Exposes a Keycloak adapter {@link AuthenticatedActionsHandler} as a Spring Security filter.
*
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
* @version $Revision: 1 $
*/
public class KeycloakAuthenticatedActionsFilter extends GenericFilterBean implements ApplicationContextAware {
private static final Logger log = LoggerFactory.getLogger(KeycloakAuthenticatedActionsFilter.class);
private final NodesRegistrationManagement management = new NodesRegistrationManagement();
private ApplicationContext applicationContext;
private AdapterDeploymentContext deploymentContext;
public KeycloakAuthenticatedActionsFilter() {
super();
}
@Override
protected void initFilterBean() throws ServletException {
deploymentContext = applicationContext.getBean(AdapterDeploymentContext.class);
}
@Override
public void destroy() {
log.debug("Unregistering deployment");
management.stop();
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpFacade facade = new SimpleHttpFacade((HttpServletRequest)request, (HttpServletResponse)response);
AuthenticatedActionsHandler handler = new AuthenticatedActionsHandler(deploymentContext.resolveDeployment(facade), (OIDCHttpFacade)facade);
boolean handled = handler.handledRequest();
if (handled) {
log.debug("Authenticated filter handled request: {}", ((HttpServletRequest) request).getRequestURI());
} else {
chain.doFilter(request, response);
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
@@ -17,13 +17,6 @@
package org.keycloak.adapters.springsecurity.filter; 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.AdapterDeploymentContext;
import org.keycloak.adapters.AdapterTokenStore; import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment; 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.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.springsecurity.KeycloakAuthenticationException; 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.authentication.SpringSecurityRequestAuthenticator;
import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade; import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
import org.keycloak.adapters.springsecurity.token.AdapterTokenStoreFactory; 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.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; 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.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher; import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert; 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. * Provides a Keycloak authentication processing filter.
* *
@@ -61,13 +61,11 @@ import org.springframework.util.Assert;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter implements ApplicationContextAware { public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter implements ApplicationContextAware {
public static final String DEFAULT_LOGIN_URL = "/sso/login"; public static final String DEFAULT_LOGIN_URL = "/sso/login";
public static final String AUTHORIZATION_HEADER = "Authorization"; public static final String AUTHORIZATION_HEADER = "Authorization";
public static final String SCHEME_BEARER = "bearer "; public static final String SCHEME_BEARER = "bearer ";
public static final String SCHEME_BASIC = "basic "; public static final String SCHEME_BASIC = "basic ";
/** /**
* Request matcher that matches requests to the {@link KeycloakAuthenticationEntryPoint#DEFAULT_LOGIN_URI default login URI} * Request matcher that matches requests to the {@link KeycloakAuthenticationEntryPoint#DEFAULT_LOGIN_URI default login URI}
* and any request with a <code>Authorization</code> header. * and any request with a <code>Authorization</code> header.
@@ -91,7 +89,7 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
*/ */
public KeycloakAuthenticationProcessingFilter(AuthenticationManager authenticationManager) { public KeycloakAuthenticationProcessingFilter(AuthenticationManager authenticationManager) {
this(authenticationManager, DEFAULT_REQUEST_MATCHER); this(authenticationManager, DEFAULT_REQUEST_MATCHER);
setAuthenticationFailureHandler(new KeycloakAuthenticationFailureHandler()); setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(DEFAULT_LOGIN_URL));
} }
/** /**
@@ -142,27 +140,8 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
log.debug("Auth outcome: {}", result); log.debug("Auth outcome: {}", result);
if (AuthOutcome.FAILED.equals(result)) { if (AuthOutcome.FAILED.equals(result)) {
AuthChallenge challenge = authenticator.getChallenge(); throw new KeycloakAuthenticationException("Auth outcome: " + result);
if (challenge != null) {
challenge.challenge(facade);
} }
throw new KeycloakAuthenticationException("Invalid authorization header, see WWW-Authenticate header for details");
}
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)) { else if (AuthOutcome.AUTHENTICATED.equals(result)) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Assert.notNull(authentication, "Authentication SecurityContextHolder was null"); Assert.notNull(authentication, "Authentication SecurityContextHolder was null");
@@ -233,6 +212,18 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
@Override @Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, 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); super.unsuccessfulAuthentication(request, response, failed);
} }
@@ -26,15 +26,6 @@ import org.springframework.mock.web.MockHttpServletResponse;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.spi.HttpFacade;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import org.mockito.Mock;
import static org.mockito.Mockito.when;
import org.mockito.MockitoAnnotations;
import org.springframework.context.ApplicationContext;
/** /**
* Keycloak authentication entry point tests. * Keycloak authentication entry point tests.
@@ -44,24 +35,12 @@ public class KeycloakAuthenticationEntryPointTest {
private KeycloakAuthenticationEntryPoint authenticationEntryPoint; private KeycloakAuthenticationEntryPoint authenticationEntryPoint;
private MockHttpServletRequest request; private MockHttpServletRequest request;
private MockHttpServletResponse response; private MockHttpServletResponse response;
@Mock
private ApplicationContext applicationContext;
@Mock
private AdapterDeploymentContext adapterDeploymentContext;
@Mock
private KeycloakDeployment keycloakDeployment;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
MockitoAnnotations.initMocks(this); authenticationEntryPoint = new KeycloakAuthenticationEntryPoint();
authenticationEntryPoint = new KeycloakAuthenticationEntryPoint(adapterDeploymentContext);
request = new MockHttpServletRequest(); request = new MockHttpServletRequest();
response = new MockHttpServletResponse(); response = new MockHttpServletResponse();
when(applicationContext.getBean(eq(AdapterDeploymentContext.class))).thenReturn(adapterDeploymentContext);
when(adapterDeploymentContext.resolveDeployment(any(HttpFacade.class))).thenReturn(keycloakDeployment);
when(keycloakDeployment.isBearerOnly()).thenReturn(Boolean.FALSE);
} }
@Test @Test
@@ -27,7 +27,6 @@ import org.keycloak.adapters.OidcKeycloakAccount;
import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.springsecurity.KeycloakAuthenticationException; import org.keycloak.adapters.springsecurity.KeycloakAuthenticationException;
import org.keycloak.adapters.springsecurity.account.KeycloakRole; import org.keycloak.adapters.springsecurity.account.KeycloakRole;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationFailureHandler;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.keycloak.common.enums.SslRequired; import org.keycloak.common.enums.SslRequired;
import org.keycloak.common.util.KeycloakUriBuilder; import org.keycloak.common.util.KeycloakUriBuilder;
@@ -91,8 +90,6 @@ public class KeycloakAuthenticationProcessingFilterTest {
@Mock @Mock
private AuthenticationFailureHandler failureHandler; private AuthenticationFailureHandler failureHandler;
private KeycloakAuthenticationFailureHandler keycloakFailureHandler;
@Mock @Mock
private OidcKeycloakAccount keycloakAccount; private OidcKeycloakAccount keycloakAccount;
@@ -109,7 +106,6 @@ public class KeycloakAuthenticationProcessingFilterTest {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
request = spy(new MockHttpServletRequest()); request = spy(new MockHttpServletRequest());
filter = new KeycloakAuthenticationProcessingFilter(authenticationManager); filter = new KeycloakAuthenticationProcessingFilter(authenticationManager);
keycloakFailureHandler = new KeycloakAuthenticationFailureHandler();
filter.setApplicationContext(applicationContext); filter.setApplicationContext(applicationContext);
filter.setAuthenticationSuccessHandler(successHandler); filter.setAuthenticationSuccessHandler(successHandler);
@@ -158,9 +154,8 @@ public class KeycloakAuthenticationProcessingFilterTest {
when(keycloakDeployment.getResourceName()).thenReturn("resource-name"); when(keycloakDeployment.getResourceName()).thenReturn("resource-name");
when(keycloakDeployment.getStateCookieName()).thenReturn("kc-cookie"); when(keycloakDeployment.getStateCookieName()).thenReturn("kc-cookie");
when(keycloakDeployment.getSslRequired()).thenReturn(SslRequired.NONE); when(keycloakDeployment.getSslRequired()).thenReturn(SslRequired.NONE);
when(keycloakDeployment.isBearerOnly()).thenReturn(Boolean.FALSE);
filter.attemptAuthentication(request, response); filter.attemptAuthentication(request, response);
verify(response).setStatus(302); verify(response).setStatus(302);
verify(response).setHeader(eq("Location"), startsWith("http://localhost:8080/auth")); verify(response).setHeader(eq("Location"), startsWith("http://localhost:8080/auth"));
} }
@@ -171,13 +166,6 @@ public class KeycloakAuthenticationProcessingFilterTest {
filter.attemptAuthentication(request, response); 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 @Test
public void testSuccessfulAuthenticationInteractive() throws Exception { public void testSuccessfulAuthenticationInteractive() throws Exception {
Authentication authentication = new KeycloakAuthenticationToken(keycloakAccount, authorities); Authentication authentication = new KeycloakAuthenticationToken(keycloakAccount, authorities);
@@ -221,7 +209,8 @@ public class KeycloakAuthenticationProcessingFilterTest {
AuthenticationException exception = new BadCredentialsException("OOPS"); AuthenticationException exception = new BadCredentialsException("OOPS");
this.setBearerAuthHeader(request); this.setBearerAuthHeader(request);
filter.unsuccessfulAuthentication(request, response, exception); 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)); any(AuthenticationException.class));
} }
@@ -230,19 +219,11 @@ public class KeycloakAuthenticationProcessingFilterTest {
AuthenticationException exception = new BadCredentialsException("OOPS"); AuthenticationException exception = new BadCredentialsException("OOPS");
this.setBasicAuthHeader(request); this.setBasicAuthHeader(request);
filter.unsuccessfulAuthentication(request, response, exception); 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)); 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) @Test(expected = UnsupportedOperationException.class)
public void testSetAllowSessionCreation() throws Exception { public void testSetAllowSessionCreation() throws Exception {
filter.setAllowSessionCreation(true); filter.setAllowSessionCreation(true);
+2 -15
View File
@@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<name>Keycloak Tomcat Integration</name> <name>Keycloak Tomcat Integration</name>
@@ -32,21 +32,8 @@
<modules> <modules>
<module>tomcat-core</module> <module>tomcat-core</module>
<module>tomcat8</module>
</modules>
<profiles>
<profile>
<id>community</id>
<activation>
<property>
<name>!product</name>
</property>
</activation>
<modules>
<module>tomcat6</module> <module>tomcat6</module>
<module>tomcat7</module> <module>tomcat7</module>
<module>tomcat8</module>
</modules> </modules>
</profile>
</profiles>
</project> </project>
+3 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-tomcat-integration-pom</artifactId> <artifactId>keycloak-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -29,6 +29,8 @@
<artifactId>keycloak-tomcat-core-adapter</artifactId> <artifactId>keycloak-tomcat-core-adapter</artifactId>
<name>Keycloak Tomcat Core Integration</name> <name>Keycloak Tomcat Core Integration</name>
<properties> <properties>
<!-- <tomcat.version>8.0.14</tomcat.version> -->
<!-- <tomcat.version>7.0.52</tomcat.version> -->
<tomcat.version>6.0.41</tomcat.version> <tomcat.version>6.0.41</tomcat.version>
</properties> </properties>
<description /> <description />
@@ -25,7 +25,6 @@ import org.apache.catalina.Manager;
import org.apache.catalina.authenticator.FormAuthenticator; import org.apache.catalina.authenticator.FormAuthenticator;
import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response; import org.apache.catalina.connector.Response;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakSecurityContext; import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterDeploymentContext; import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.AdapterTokenStore; import org.keycloak.adapters.AdapterTokenStore;
@@ -49,6 +48,8 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* Keycloak authentication valve * Keycloak authentication valve
@@ -61,7 +62,7 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
public static final String TOKEN_STORE_NOTE = "TOKEN_STORE_NOTE"; public static final String TOKEN_STORE_NOTE = "TOKEN_STORE_NOTE";
private final static Logger log = Logger.getLogger(AbstractKeycloakAuthenticatorValve.class); private final static Logger log = Logger.getLogger(""+AbstractKeycloakAuthenticatorValve.class);
protected CatalinaUserSessionManagement userSessionManagement = new CatalinaUserSessionManagement(); protected CatalinaUserSessionManagement userSessionManagement = new CatalinaUserSessionManagement();
protected AdapterDeploymentContext deploymentContext; protected AdapterDeploymentContext deploymentContext;
protected NodesRegistrationManagement nodesRegistrationManagement; protected NodesRegistrationManagement nodesRegistrationManagement;
@@ -117,22 +118,22 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
try { try {
KeycloakConfigResolver configResolver = (KeycloakConfigResolver) context.getLoader().getClassLoader().loadClass(configResolverClass).newInstance(); KeycloakConfigResolver configResolver = (KeycloakConfigResolver) context.getLoader().getClassLoader().loadClass(configResolverClass).newInstance();
deploymentContext = new AdapterDeploymentContext(configResolver); deploymentContext = new AdapterDeploymentContext(configResolver);
log.debugv("Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass); log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass);
} catch (Exception ex) { } catch (Exception ex) {
log.errorv("The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", configResolverClass, ex.getMessage()); 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()); deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
} }
} else { } else {
InputStream configInputStream = getConfigInputStream(context); InputStream configInputStream = getConfigInputStream(context);
KeycloakDeployment kd; KeycloakDeployment kd;
if (configInputStream == null) { if (configInputStream == null) {
log.warn("No adapter configuration. Keycloak is unconfigured and will deny all requests."); log.warning("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
kd = new KeycloakDeployment(); kd = new KeycloakDeployment();
} else { } else {
kd = KeycloakDeploymentBuilder.build(configInputStream); kd = KeycloakDeploymentBuilder.build(configInputStream);
} }
deploymentContext = new AdapterDeploymentContext(kd); deploymentContext = new AdapterDeploymentContext(kd);
log.debug("Keycloak is using a per-deployment configuration."); log.fine("Keycloak is using a per-deployment configuration.");
} }
context.getServletContext().setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext); context.getServletContext().setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
@@ -148,7 +149,8 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
if (json == null) { if (json == null) {
return null; return null;
} }
log.trace("**** using " + AdapterConstants.AUTH_DATA_PARAM_NAME); log.finest("**** using " + AdapterConstants.AUTH_DATA_PARAM_NAME);
log.finest(json);
return new ByteArrayInputStream(json.getBytes()); return new ByteArrayInputStream(json.getBytes());
} }
@@ -157,13 +159,13 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
if (is == null) { if (is == null) {
String path = context.getServletContext().getInitParameter("keycloak.config.file"); String path = context.getServletContext().getInitParameter("keycloak.config.file");
if (path == null) { if (path == null) {
log.trace("**** using /WEB-INF/keycloak.json"); log.finest("**** using /WEB-INF/keycloak.json");
is = context.getServletContext().getResourceAsStream("/WEB-INF/keycloak.json"); is = context.getServletContext().getResourceAsStream("/WEB-INF/keycloak.json");
} else { } else {
try { try {
is = new FileInputStream(path); is = new FileInputStream(path);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
log.errorv("NOT FOUND {0}", path); log.log(Level.SEVERE, "NOT FOUND {0}", path);
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-tomcat-integration-pom</artifactId> <artifactId>keycloak-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-tomcat-integration-pom</artifactId> <artifactId>keycloak-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-tomcat-integration-pom</artifactId> <artifactId>keycloak-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.4.0.Final</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -173,15 +173,10 @@ public class KeycloakServletExtension implements ServletExtension {
} }
}); });
ServletSessionConfig cookieConfig = deploymentInfo.getServletSessionConfig();
if (cookieConfig == null) {
cookieConfig = new ServletSessionConfig();
}
if (cookieConfig.getPath() == null) {
log.debug("Setting jsession cookie path to: " + deploymentInfo.getContextPath()); log.debug("Setting jsession cookie path to: " + deploymentInfo.getContextPath());
ServletSessionConfig cookieConfig = new ServletSessionConfig();
cookieConfig.setPath(deploymentInfo.getContextPath()); cookieConfig.setPath(deploymentInfo.getContextPath());
deploymentInfo.setServletSessionConfig(cookieConfig); deploymentInfo.setServletSessionConfig(cookieConfig);
}
ChangeSessionId.turnOffChangeSessionIdOnLogin(deploymentInfo); ChangeSessionId.turnOffChangeSessionIdOnLogin(deploymentInfo);
deploymentInfo.addListener(new ListenerInfo(UndertowNodesRegistrationManagementWrapper.class, new InstanceFactory<UndertowNodesRegistrationManagementWrapper>() { deploymentInfo.addListener(new ListenerInfo(UndertowNodesRegistrationManagementWrapper.class, new InstanceFactory<UndertowNodesRegistrationManagementWrapper>() {
-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.0.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());
}
}
Loaded 100 of 3649 files, more files were not shown because too many files have changed in this diff. Show more