Compare commits
50
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81b9e99c5d | ||
|
|
a4a6dd5049 | ||
|
|
401b2508a8 | ||
|
|
709e7a6c9d | ||
|
|
1146d8dc5b | ||
|
|
7e4eb7e87c | ||
|
|
c93eee1370 | ||
|
|
7ae154562e | ||
|
|
14fdb38dbe | ||
|
|
a6c2ae2c52 | ||
|
|
98c1020d4c | ||
|
|
23791a8efd | ||
|
|
52366aecc8 | ||
|
|
4c7f40c32c | ||
|
|
1f5a4a6758 | ||
|
|
8cb17ed292 | ||
|
|
68ca3d4959 | ||
|
|
33742dbf62 | ||
|
|
86fb74f00f | ||
|
|
ac7b5a9708 | ||
|
|
e45c7ce19f | ||
|
|
dde05cdb0e | ||
|
|
890f9aa393 | ||
|
|
83804d5f2e | ||
|
|
244f6ecbc7 | ||
|
|
ac8ab80e01 | ||
|
|
dcb56d6774 | ||
|
|
a57aa6257f | ||
|
|
b913a18106 | ||
|
|
27f9ec9381 | ||
|
|
7a24fd87b0 | ||
|
|
9c8443a5fc | ||
|
|
3a466df3ac | ||
|
|
e6f4e678a0 | ||
|
|
51868ba3bf | ||
|
|
6fc1909cf0 | ||
|
|
59ad412159 | ||
|
|
42415db551 | ||
|
|
865af553cf | ||
|
|
18d45cc325 | ||
|
|
b697e3d4a9 | ||
|
|
7d43e405d6 | ||
|
|
cc195c469f | ||
|
|
8c5330f32e | ||
|
|
8a5658a86c | ||
|
|
7b0693671c | ||
|
|
aea45e46da | ||
|
|
243ce2fc65 | ||
|
|
1200955643 | ||
|
|
1ad464c681 |
No files matched your search
@@ -49,7 +49,3 @@ target
|
|||||||
# Maven shade
|
# Maven shade
|
||||||
#############
|
#############
|
||||||
*dependency-reduced-pom.xml
|
*dependency-reduced-pom.xml
|
||||||
|
|
||||||
# nodejs #
|
|
||||||
##########
|
|
||||||
node_modules
|
|
||||||
+17
-10
@@ -1,27 +1,34 @@
|
|||||||
language: java
|
language: java
|
||||||
dist: precise
|
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
cache: false
|
directories:
|
||||||
|
- $HOME/.m2
|
||||||
|
|
||||||
|
before_cache:
|
||||||
|
- rm -rf $HOME/.m2/repository/org/keycloak
|
||||||
|
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- MAVEN_SKIP_RC=true
|
- MAVEN_SKIP_RC=true
|
||||||
- MAVEN_OPTS="-Xms512m -Xmx1536m"
|
- MAVEN_OPTS="-Xms512m -Xmx2048m"
|
||||||
matrix:
|
matrix:
|
||||||
- TESTS=unit
|
- TESTS=group1
|
||||||
- TESTS=server-group1
|
- TESTS=group2
|
||||||
- TESTS=server-group2
|
- TESTS=group3
|
||||||
- TESTS=server-group3
|
- TESTS=group4
|
||||||
- TESTS=server-group4
|
- TESTS=adapter
|
||||||
- TESTS=old
|
- TESTS=old
|
||||||
|
|
||||||
jdk:
|
jdk:
|
||||||
- oraclejdk8
|
- oraclejdk8
|
||||||
|
|
||||||
install: true
|
before_script:
|
||||||
|
- export MAVEN_SKIP_RC=true
|
||||||
|
|
||||||
script:
|
install:
|
||||||
|
- travis_wait 60 mvn install -Pdistribution -DskipTests=true -B -V -q
|
||||||
|
|
||||||
|
script:
|
||||||
- ./travis-run-tests.sh $TESTS
|
- ./travis-run-tests.sh $TESTS
|
||||||
|
|
||||||
sudo: false
|
sudo: false
|
||||||
@@ -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`.
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>3.3.0.CR1</version>
|
<version>2.5.4.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
-4
@@ -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";
|
|
||||||
}
|
}
|
||||||
+3
-3
@@ -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) {
|
||||||
|
|||||||
-44
@@ -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 {
|
||||||
@@ -75,7 +73,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;
|
||||||
@@ -88,12 +85,6 @@ public class KeycloakDeployment {
|
|||||||
protected int publicKeyCacheTtl;
|
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() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,14 +322,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;
|
||||||
}
|
}
|
||||||
@@ -431,31 +414,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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
-10
@@ -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,12 +96,6 @@ 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());
|
||||||
@@ -115,8 +107,6 @@ public class KeycloakDeploymentBuilder {
|
|||||||
deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive());
|
deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive());
|
||||||
deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests());
|
deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests());
|
||||||
deployment.setPublicKeyCacheTtl(adapterConfig.getPublicKeyCacheTtl());
|
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");
|
||||||
|
|||||||
+5
-26
@@ -25,6 +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.common.VerificationException;
|
import org.keycloak.common.VerificationException;
|
||||||
|
import org.keycloak.common.util.Encode;
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||||
import org.keycloak.common.util.UriUtils;
|
import org.keycloak.common.util.UriUtils;
|
||||||
import org.keycloak.constants.AdapterConstants;
|
import org.keycloak.constants.AdapterConstants;
|
||||||
@@ -37,10 +38,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 +141,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 +170,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, Encode.encodeQueryParamAsIs(url)) // Need to encode uri ourselves as queryParam() will not encode % characters.
|
||||||
.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){
|
||||||
@@ -323,11 +320,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());
|
||||||
@@ -379,23 +375,6 @@ public class OAuthRequestAuthenticator {
|
|||||||
.replaceQueryParam(OAuth2Constants.STATE, null);
|
.replaceQueryParam(OAuth2Constants.STATE, null);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
-5
@@ -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()) {
|
||||||
|
|||||||
+1
-3
@@ -155,9 +155,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
|
|||||||
this.refreshToken = response.getRefreshToken();
|
this.refreshToken = response.getRefreshToken();
|
||||||
}
|
}
|
||||||
this.tokenString = tokenString;
|
this.tokenString = tokenString;
|
||||||
if (tokenStore != null) {
|
tokenStore.refreshCallback(this);
|
||||||
tokenStore.refreshCallback(this);
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-3
@@ -24,7 +24,6 @@ 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>
|
||||||
@@ -161,8 +160,7 @@ 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;
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
+45
-25
@@ -17,6 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.keycloak.adapters.authorization;
|
package org.keycloak.adapters.authorization;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -28,8 +29,10 @@ 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;
|
||||||
@@ -53,7 +56,7 @@ 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 = policyEnforcer.getPaths();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,6 +74,7 @@ public abstract class AbstractPolicyEnforcer {
|
|||||||
|
|
||||||
if (accessToken != null) {
|
if (accessToken != null) {
|
||||||
Request request = httpFacade.getRequest();
|
Request request = httpFacade.getRequest();
|
||||||
|
Response response = httpFacade.getResponse();
|
||||||
String path = getPath(request);
|
String path = getPath(request);
|
||||||
PathConfig pathConfig = this.pathMatcher.matches(path, this.paths);
|
PathConfig pathConfig = this.pathMatcher.matches(path, this.paths);
|
||||||
|
|
||||||
@@ -78,16 +82,11 @@ public abstract class AbstractPolicyEnforcer {
|
|||||||
|
|
||||||
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 +95,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
|
if (!challenge(actualPathConfig, requiredScopes, httpFacade)) {
|
||||||
|
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
|
||||||
if (!challenge(pathConfig, requiredScopes, httpFacade)) {
|
response.sendError(403, "Authorization failed.");
|
||||||
LOGGER.debugf("Challenge not sent, sending default forbidden response. Path [%s]", pathConfig);
|
|
||||||
handleAccessDenied(httpFacade);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,10 +169,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) {
|
private boolean isDefaultAccessDeniedUri(Request request, PolicyEnforcerConfig enforcerConfig) {
|
||||||
String accessDeniedPath = enforcerConfig.getOnDenyRedirectTo();
|
String accessDeniedPath = enforcerConfig.getOnDenyRedirectTo();
|
||||||
|
|
||||||
@@ -204,7 +198,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,6 +226,32 @@ public abstract class AbstractPolicyEnforcer {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PathConfig resolvePathConfig(PathConfig originalConfig, Request request) {
|
||||||
|
String path = getPath(request);
|
||||||
|
|
||||||
|
if (originalConfig.hasPattern()) {
|
||||||
|
ProtectedResource resource = this.authzClient.protection().resource();
|
||||||
|
Set<String> search = resource.findByFilter("uri=" + path);
|
||||||
|
|
||||||
|
if (!search.isEmpty()) {
|
||||||
|
// resource does exist on the server, cache it
|
||||||
|
ResourceRepresentation targetResource = resource.findById(search.iterator().next()).getResourceDescription();
|
||||||
|
PathConfig config = PolicyEnforcer.createPathConfig(targetResource);
|
||||||
|
|
||||||
|
config.setScopes(originalConfig.getScopes());
|
||||||
|
config.setMethods(originalConfig.getMethods());
|
||||||
|
config.setParentConfig(originalConfig);
|
||||||
|
config.setEnforcementMode(originalConfig.getEnforcementMode());
|
||||||
|
|
||||||
|
this.policyEnforcer.addPath(config);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return originalConfig;
|
||||||
|
}
|
||||||
|
|
||||||
private String getPath(Request request) {
|
private String getPath(Request request) {
|
||||||
return request.getRelativePath();
|
return request.getRelativePath();
|
||||||
}
|
}
|
||||||
@@ -252,8 +272,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) {
|
||||||
|
|||||||
+4
-10
@@ -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) {
|
||||||
|
|||||||
+4
-8
@@ -83,12 +83,6 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
|||||||
|
|
||||||
@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 +92,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 +123,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();
|
||||||
@@ -137,7 +133,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
|||||||
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());
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-170
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+48
-181
@@ -17,226 +17,93 @@
|
|||||||
*/
|
*/
|
||||||
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.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @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, Map<String, PathConfig> paths) {
|
||||||
this.authzClient = authzClient;
|
PathConfig pathConfig = paths.get(requestedUri);
|
||||||
}
|
|
||||||
|
|
||||||
public PathConfig matches(final String targetUri, Map<String, PathConfig> paths) {
|
|
||||||
PathConfig pathConfig = paths.get(targetUri) == null ? cache.get(targetUri) : paths.get(targetUri);
|
|
||||||
|
|
||||||
if (pathConfig != null) {
|
if (pathConfig != null) {
|
||||||
return pathConfig;
|
return pathConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
PathConfig matchingAnyPath = null;
|
PathConfig actualConfig = null;
|
||||||
PathConfig matchingAnySuffixPath = null;
|
|
||||||
|
|
||||||
for (PathConfig entry : paths.values()) {
|
for (PathConfig entry : paths.values()) {
|
||||||
String expectedUri = entry.getPath();
|
String protectedUri = entry.getPath();
|
||||||
String matchingUri = null;
|
String selectedUri = null;
|
||||||
|
|
||||||
if (exactMatch(expectedUri, targetUri, expectedUri)) {
|
if (protectedUri.equals(ANY_RESOURCE_PATTERN) && actualConfig == null) {
|
||||||
matchingUri = expectedUri;
|
selectedUri = protectedUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTemplate(expectedUri)) {
|
int suffixIndex = protectedUri.indexOf(ANY_RESOURCE_PATTERN + ".");
|
||||||
String templateUri = buildUriFromTemplate(expectedUri, targetUri);
|
|
||||||
|
|
||||||
if (templateUri != null) {
|
if (suffixIndex != -1) {
|
||||||
if (exactMatch(expectedUri, targetUri, templateUri)) {
|
String protectedSuffix = protectedUri.substring(suffixIndex + ANY_RESOURCE_PATTERN.length());
|
||||||
matchingUri = templateUri;
|
|
||||||
entry = resolvePathConfig(entry, targetUri);
|
if (requestedUri.endsWith(protectedSuffix)) {
|
||||||
}
|
selectedUri = protectedUri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchingUri != null) {
|
if (protectedUri.equals(requestedUri)) {
|
||||||
StringBuilder path = new StringBuilder(expectedUri);
|
selectedUri = protectedUri;
|
||||||
int patternIndex = path.indexOf("/" + WILDCARD);
|
}
|
||||||
|
|
||||||
if (patternIndex != -1) {
|
if (protectedUri.endsWith(ANY_RESOURCE_PATTERN)) {
|
||||||
path.delete(patternIndex, path.length());
|
String formattedPattern = removeWildCardsFromUri(protectedUri);
|
||||||
|
|
||||||
|
if (!formattedPattern.equals("/") && requestedUri.startsWith(formattedPattern)) {
|
||||||
|
selectedUri = protectedUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
patternIndex = path.indexOf("{");
|
if (!formattedPattern.equals("/") && formattedPattern.endsWith("/") && formattedPattern.substring(0, formattedPattern.length() - 1).equals(requestedUri)) {
|
||||||
|
selectedUri = protectedUri;
|
||||||
if (patternIndex != -1) {
|
|
||||||
path.delete(patternIndex, path.length());
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String pathString = path.toString();
|
int startRegex = protectedUri.indexOf('{');
|
||||||
|
|
||||||
if ("".equals(pathString)) {
|
if (startRegex != -1) {
|
||||||
pathString = "/";
|
String prefix = protectedUri.substring(0, startRegex);
|
||||||
|
|
||||||
|
if (requestedUri.startsWith(prefix)) {
|
||||||
|
selectedUri = protectedUri;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (matchingUri.equals(targetUri) || pathString.equals(targetUri)) {
|
if (selectedUri != null) {
|
||||||
cache.put(targetUri, entry);
|
selectedUri = protectedUri;
|
||||||
return entry;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (WILDCARD == expectedUri.charAt(expectedUri.length() - 1)) {
|
if (selectedUri != null) {
|
||||||
matchingAnyPath = entry;
|
if (actualConfig == null) {
|
||||||
|
actualConfig = entry;
|
||||||
} else {
|
} else {
|
||||||
int suffixIndex = expectedUri.indexOf(WILDCARD + ".");
|
if (actualConfig.equals(ANY_RESOURCE_PATTERN)) {
|
||||||
|
actualConfig = entry;
|
||||||
|
}
|
||||||
|
|
||||||
if (suffixIndex != -1) {
|
if (protectedUri.startsWith(removeWildCardsFromUri(actualConfig.getPath()))) {
|
||||||
String protectedSuffix = expectedUri.substring(suffixIndex + 1);
|
actualConfig = entry;
|
||||||
|
|
||||||
if (targetUri.endsWith(protectedSuffix)) {
|
|
||||||
matchingAnySuffixPath = entry;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchingAnySuffixPath != null) {
|
return actualConfig;
|
||||||
cache.put(targetUri, matchingAnySuffixPath);
|
|
||||||
return matchingAnySuffixPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matchingAnyPath != null) {
|
|
||||||
cache.put(targetUri, matchingAnyPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
return matchingAnyPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean exactMatch(String expectedUri, String targetUri, String value) {
|
private String removeWildCardsFromUri(String protectedUri) {
|
||||||
if (targetUri.equals(value)) {
|
return protectedUri.replaceAll("/[*]", "/");
|
||||||
return value.equals(targetUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (endsWithWildcard(expectedUri)) {
|
|
||||||
return targetUri.startsWith(expectedUri.substring(0, expectedUri.length() - 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
String suffix = "/*.";
|
|
||||||
int suffixIndex = expectedUri.indexOf(suffix);
|
|
||||||
|
|
||||||
if (suffixIndex != -1) {
|
|
||||||
return targetUri.endsWith(expectedUri.substring(suffixIndex + suffix.length() - 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String buildUriFromTemplate(String expectedUri, String targetUri) {
|
|
||||||
int patternStartIndex = expectedUri.indexOf("{");
|
|
||||||
|
|
||||||
if (patternStartIndex == -1 || patternStartIndex >= targetUri.length()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expectedUri.split("/").length > targetUri.split("/").length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
char[] expectedUriChars = expectedUri.toCharArray();
|
|
||||||
char[] matchingUri = Arrays.copyOfRange(expectedUriChars, 0, patternStartIndex);
|
|
||||||
int matchingUriLastIndex = matchingUri.length;
|
|
||||||
String targetUriParams = targetUri.substring(patternStartIndex);
|
|
||||||
|
|
||||||
if (Arrays.equals(matchingUri, Arrays.copyOf(targetUri.toCharArray(), matchingUri.length))) {
|
|
||||||
matchingUri = Arrays.copyOf(matchingUri, targetUri.length());
|
|
||||||
int paramIndex = 0;
|
|
||||||
|
|
||||||
for (int i = patternStartIndex; i < expectedUriChars.length; i++) {
|
|
||||||
if (matchingUriLastIndex >= matchingUri.length) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
char c = expectedUriChars[i];
|
|
||||||
|
|
||||||
if (c == '{' || c == '*') {
|
|
||||||
String[] params = targetUriParams.split("/");
|
|
||||||
|
|
||||||
for (int k = paramIndex; k <= (c == '*' ? params.length : paramIndex); k++) {
|
|
||||||
if (k == params.length) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
int paramLength = params[k].length();
|
|
||||||
|
|
||||||
if (matchingUriLastIndex + paramLength > matchingUri.length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int j = 0; j < paramLength; j++) {
|
|
||||||
matchingUri[matchingUriLastIndex++] = params[k].charAt(j);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c == '*' && matchingUriLastIndex < matchingUri.length) {
|
|
||||||
matchingUri[matchingUriLastIndex++] = '/';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
i = expectedUri.indexOf('}', i);
|
|
||||||
} else {
|
|
||||||
if (c == '/') {
|
|
||||||
paramIndex++;
|
|
||||||
}
|
|
||||||
matchingUri[matchingUriLastIndex++] = c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matchingUri[matchingUri.length - 1] == '\u0000') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return String.valueOf(matchingUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean endsWithWildcard(String expectedUri) {
|
|
||||||
return WILDCARD == expectedUri.charAt(expectedUri.length() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isTemplate(String uri) {
|
|
||||||
return uri.indexOf("{") != -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PathConfig resolvePathConfig(PathConfig originalConfig, String path) {
|
|
||||||
if (originalConfig.hasPattern()) {
|
|
||||||
ProtectedResource resource = this.authzClient.protection().resource();
|
|
||||||
Set<String> search = resource.findByFilter("uri=" + path);
|
|
||||||
|
|
||||||
if (!search.isEmpty()) {
|
|
||||||
// resource does exist on the server, cache it
|
|
||||||
ResourceRepresentation targetResource = resource.findById(search.iterator().next()).getResourceDescription();
|
|
||||||
PathConfig config = PolicyEnforcer.createPathConfig(targetResource);
|
|
||||||
|
|
||||||
config.setScopes(originalConfig.getScopes());
|
|
||||||
config.setMethods(originalConfig.getMethods());
|
|
||||||
config.setParentConfig(originalConfig);
|
|
||||||
config.setEnforcementMode(originalConfig.getEnforcementMode());
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return originalConfig;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-22
@@ -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;
|
||||||
@@ -53,18 +51,11 @@ public class PolicyEnforcer {
|
|||||||
private final AuthzClient authzClient;
|
private final AuthzClient authzClient;
|
||||||
private final PolicyEnforcerConfig enforcerConfig;
|
private final PolicyEnforcerConfig enforcerConfig;
|
||||||
private final Map<String, PathConfig> paths;
|
private final Map<String, 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()) {
|
||||||
@@ -227,14 +218,7 @@ public class PolicyEnforcer {
|
|||||||
|
|
||||||
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 +231,4 @@ public class PolicyEnforcer {
|
|||||||
|
|
||||||
return pathConfig;
|
return pathConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PathMatcher getPathMatcher() {
|
|
||||||
return pathMatcher;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
-6
@@ -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());
|
||||||
@@ -75,7 +70,6 @@ public class KeycloakDeploymentBuilderTest {
|
|||||||
assertEquals(10, deployment.getTokenMinimumTimeToLive());
|
assertEquals(10, deployment.getTokenMinimumTimeToLive());
|
||||||
assertEquals(20, deployment.getMinTimeBetweenJwksRequests());
|
assertEquals(20, deployment.getMinTimeBetweenJwksRequests());
|
||||||
assertEquals(120, deployment.getPublicKeyCacheTtl());
|
assertEquals(120, deployment.getPublicKeyCacheTtl());
|
||||||
assertEquals("/api/$1", deployment.getRedirectRewriteRules().get("^/wsmaster/api/(.*)$"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
-49
@@ -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,
|
||||||
@@ -32,9 +31,5 @@
|
|||||||
"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,
|
"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.3.0.CR1</version>
|
<version>2.5.4.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -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.3.0.CR1</version>
|
<version>2.5.4.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -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.3.0.CR1</version>
|
<version>2.5.4.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>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>3.3.0.CR1</version>
|
<version>2.5.4.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,9 +0,0 @@
|
|||||||
CLI Single Sign On
|
|
||||||
===================================
|
|
||||||
|
|
||||||
This java-based utility is meant for providing Keycloak integration to
|
|
||||||
command line applications that are either written in Java or another language. The
|
|
||||||
idea is that the Java app provided by this utility performs a login for a specific
|
|
||||||
client, parses responses, and exports an access token as an environment variable
|
|
||||||
that can be used by the command line utility you are accessing.
|
|
||||||
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
export KC_AUTH_SERVER=http://localhost:8080/auth
|
|
||||||
export KC_REALM=master
|
|
||||||
export KC_CLIENT=cli
|
|
||||||
|
|
||||||
export KC_ACCESS_TOKEN=`java -DKEYCLOAK_AUTH_SERVER=$KC_AUTH_SERVER -DKEYCLOAK_REALM=$KC_REALM -DKEYCLOAK_CLIENT=$KC_CLIENT -jar target/keycloak-cli-sso-3.3.0.CR1-SNAPSHOT.jar login`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
java -DKEYCLOAK_AUTH_SERVER=$KC_AUTH_SERVER -DKEYCLOAK_REALM=$KC_REALM -DKEYCLOAK_CLIENT=$KC_CLIENT -jar target/keycloak-cli-sso-3.3.0.CR1-SNAPSHOT.jar logout
|
|
||||||
|
|
||||||
unset KC_ACCESS_TOKEN
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,84 +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.3.0.CR1</version>
|
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>keycloak-cli-sso</artifactId>
|
|
||||||
<name>Keycloak CLI SSO Framework</name>
|
|
||||||
<description/>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-installed-adapter</artifactId>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<source>${maven.compiler.source}</source>
|
|
||||||
<target>${maven.compiler.target}</target>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-shade-plugin</artifactId>
|
|
||||||
<version>3.0.0</version>
|
|
||||||
<configuration>
|
|
||||||
<transformers>
|
|
||||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
|
||||||
<mainClass>org.keycloak.adapters.KeycloakCliSsoMain</mainClass>
|
|
||||||
</transformer>
|
|
||||||
</transformers>
|
|
||||||
|
|
||||||
<filters>
|
|
||||||
<filter>
|
|
||||||
<artifact>*:*</artifact>
|
|
||||||
<excludes>
|
|
||||||
<exclude>META-INF/*.SF</exclude>
|
|
||||||
<exclude>META-INF/*.DSA</exclude>
|
|
||||||
<exclude>META-INF/*.RSA</exclude>
|
|
||||||
</excludes>
|
|
||||||
</filter>
|
|
||||||
</filters>
|
|
||||||
</configuration>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>shade</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
|
|
||||||
</project>
|
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>3.3.0.CR1</version>
|
<version>2.5.4.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
-266
@@ -1,266 +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.installed;
|
|
||||||
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
|
||||||
import org.keycloak.adapters.ServerRequest;
|
|
||||||
import org.keycloak.common.util.Time;
|
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
|
||||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
|
||||||
import org.keycloak.util.JsonSerialization;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class KeycloakCliSso {
|
|
||||||
|
|
||||||
public void mainCmd(String[] args) throws Exception {
|
|
||||||
if (args.length != 1) {
|
|
||||||
printHelp();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args[0].equalsIgnoreCase("login")) {
|
|
||||||
login();
|
|
||||||
} else if (args[0].equalsIgnoreCase("login-manual")) {
|
|
||||||
loginManual();
|
|
||||||
} else if (args[0].equalsIgnoreCase("token")) {
|
|
||||||
token();
|
|
||||||
} else if (args[0].equalsIgnoreCase("logout")) {
|
|
||||||
logout();
|
|
||||||
} else if (args[0].equalsIgnoreCase("env")) {
|
|
||||||
System.out.println(System.getenv().toString());
|
|
||||||
} else {
|
|
||||||
printHelp();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void printHelp() {
|
|
||||||
System.err.println("Commands:");
|
|
||||||
System.err.println(" login - login with desktop browser if available, otherwise do manual login. Output is access token.");
|
|
||||||
System.err.println(" login-manual - manual login");
|
|
||||||
System.err.println(" token - print access token if logged in");
|
|
||||||
System.err.println(" logout - logout.");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AdapterConfig getConfig() {
|
|
||||||
String url = System.getProperty("KEYCLOAK_AUTH_SERVER");
|
|
||||||
if (url == null) {
|
|
||||||
System.err.println("KEYCLOAK_AUTH_SERVER property not set");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
String realm = System.getProperty("KEYCLOAK_REALM");
|
|
||||||
if (realm == null) {
|
|
||||||
System.err.println("KEYCLOAK_REALM property not set");
|
|
||||||
System.exit(1);
|
|
||||||
|
|
||||||
}
|
|
||||||
String client = System.getProperty("KEYCLOAK_CLIENT");
|
|
||||||
if (client == null) {
|
|
||||||
System.err.println("KEYCLOAK_CLIENT property not set");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
String secret = System.getProperty("KEYCLOAK_CLIENT_SECRET");
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
AdapterConfig config = new AdapterConfig();
|
|
||||||
config.setAuthServerUrl(url);
|
|
||||||
config.setRealm(realm);
|
|
||||||
config.setResource(client);
|
|
||||||
config.setSslRequired("external");
|
|
||||||
if (secret != null) {
|
|
||||||
Map<String, Object> creds = new HashMap<>();
|
|
||||||
creds.put("secret", secret);
|
|
||||||
config.setCredentials(creds);
|
|
||||||
} else {
|
|
||||||
config.setPublicClient(true);
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean checkToken() throws Exception {
|
|
||||||
String token = getTokenResponse();
|
|
||||||
if (token == null) return false;
|
|
||||||
|
|
||||||
|
|
||||||
if (token != null) {
|
|
||||||
Matcher m = Pattern.compile("\\{.*\\}\\z").matcher(token);
|
|
||||||
if (m.find()) {
|
|
||||||
String json = m.group(0);
|
|
||||||
try {
|
|
||||||
AccessTokenResponse tokenResponse = JsonSerialization.readValue(json, AccessTokenResponse.class);
|
|
||||||
if (Time.currentTime() < tokenResponse.getExpiresIn()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
AdapterConfig config = getConfig();
|
|
||||||
KeycloakInstalled installed = new KeycloakInstalled(KeycloakDeploymentBuilder.build(config));
|
|
||||||
installed.refreshToken(tokenResponse.getRefreshToken());
|
|
||||||
processResponse(installed);
|
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.err.println("Error processing existing token");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getTokenResponse() throws IOException {
|
|
||||||
String token = null;
|
|
||||||
File tokenFile = getTokenFilePath();
|
|
||||||
if (tokenFile.exists()) {
|
|
||||||
FileInputStream fis = new FileInputStream(tokenFile);
|
|
||||||
byte[] data = new byte[(int) tokenFile.length()];
|
|
||||||
fis.read(data);
|
|
||||||
fis.close();
|
|
||||||
token = new String(data, "UTF-8");
|
|
||||||
}
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void token() throws Exception {
|
|
||||||
String token = getTokenResponse();
|
|
||||||
if (token == null) {
|
|
||||||
System.err.println("There is no token for client");
|
|
||||||
System.exit(1);
|
|
||||||
} else {
|
|
||||||
Matcher m = Pattern.compile("\\{.*\\}\\z").matcher(token);
|
|
||||||
if (m.find()) {
|
|
||||||
String json = m.group(0);
|
|
||||||
try {
|
|
||||||
AccessTokenResponse tokenResponse = JsonSerialization.readValue(json, AccessTokenResponse.class);
|
|
||||||
if (Time.currentTime() < tokenResponse.getExpiresIn()) {
|
|
||||||
System.out.println(tokenResponse.getToken());
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
System.err.println("token in response file is expired");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.err.println("Failure processing token response file");
|
|
||||||
e.printStackTrace();
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
System.err.println("Could not find json within token response file");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void login() throws Exception {
|
|
||||||
if (checkToken()) return;
|
|
||||||
AdapterConfig config = getConfig();
|
|
||||||
KeycloakInstalled installed = new KeycloakInstalled(KeycloakDeploymentBuilder.build(config));
|
|
||||||
installed.login();
|
|
||||||
processResponse(installed);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getHome() {
|
|
||||||
String home = System.getenv("HOME");
|
|
||||||
if (home == null) {
|
|
||||||
home = System.getProperty("HOME");
|
|
||||||
if (home == null) {
|
|
||||||
home = Paths.get("").toAbsolutePath().normalize().toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return home;
|
|
||||||
}
|
|
||||||
|
|
||||||
public File getTokenDirectory() {
|
|
||||||
return Paths.get(getHome(), System.getProperty("basepath", ".keycloak-sso"), System.getProperty("KEYCLOAK_REALM")).toFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
public File getTokenFilePath() {
|
|
||||||
return Paths.get(getHome(), System.getProperty("basepath", ".keycloak-sso"), System.getProperty("KEYCLOAK_REALM"), System.getProperty("KEYCLOAK_CLIENT") + ".json").toFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processResponse(KeycloakInstalled installed) throws IOException {
|
|
||||||
AccessTokenResponse tokenResponse = installed.getTokenResponse();
|
|
||||||
tokenResponse.setExpiresIn(Time.currentTime() + tokenResponse.getExpiresIn());
|
|
||||||
tokenResponse.setIdToken(null);
|
|
||||||
String output = JsonSerialization.writeValueAsString(tokenResponse);
|
|
||||||
getTokenDirectory().mkdirs();
|
|
||||||
FileOutputStream fos = new FileOutputStream(getTokenFilePath());
|
|
||||||
fos.write(output.getBytes("UTF-8"));
|
|
||||||
fos.flush();
|
|
||||||
fos.close();
|
|
||||||
System.out.println(tokenResponse.getToken());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loginManual() throws Exception {
|
|
||||||
if (checkToken()) return;
|
|
||||||
AdapterConfig config = getConfig();
|
|
||||||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(config);
|
|
||||||
KeycloakInstalled installed = new KeycloakInstalled(deployment);
|
|
||||||
installed.loginManual();
|
|
||||||
processResponse(installed);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void logout() throws Exception {
|
|
||||||
String token = getTokenResponse();
|
|
||||||
if (token != null) {
|
|
||||||
Matcher m = Pattern.compile("\\{.*\\}\\z").matcher(token);
|
|
||||||
if (m.find()) {
|
|
||||||
String json = m.group(0);
|
|
||||||
try {
|
|
||||||
AccessTokenResponse tokenResponse = JsonSerialization.readValue(json, AccessTokenResponse.class);
|
|
||||||
if (Time.currentTime() > tokenResponse.getExpiresIn()) {
|
|
||||||
System.err.println("Login is expired");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
AdapterConfig config = getConfig();
|
|
||||||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(config);
|
|
||||||
ServerRequest.invokeLogout(deployment, tokenResponse.getRefreshToken());
|
|
||||||
for (File fp : getTokenDirectory().listFiles()) fp.delete();
|
|
||||||
System.out.println("logout complete");
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.err.println("Failure processing token response file");
|
|
||||||
e.printStackTrace();
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
System.err.println("Could not find json within token response file");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
System.err.println("Not logged in");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+13
-139
@@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
package org.keycloak.adapters.installed;
|
package org.keycloak.adapters.installed;
|
||||||
|
|
||||||
import org.apache.commons.codec.Charsets;
|
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.OAuthErrorException;
|
import org.keycloak.OAuthErrorException;
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
@@ -25,7 +24,6 @@ import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
|||||||
import org.keycloak.adapters.ServerRequest;
|
import org.keycloak.adapters.ServerRequest;
|
||||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||||
import org.keycloak.common.VerificationException;
|
import org.keycloak.common.VerificationException;
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
import org.keycloak.jose.jws.JWSInputException;
|
import org.keycloak.jose.jws.JWSInputException;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
@@ -45,7 +43,6 @@ import java.net.ServerSocket;
|
|||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@@ -54,11 +51,6 @@ import java.util.concurrent.TimeUnit;
|
|||||||
*/
|
*/
|
||||||
public class KeycloakInstalled {
|
public class KeycloakInstalled {
|
||||||
|
|
||||||
public interface HttpResponseWriter {
|
|
||||||
void success(PrintWriter pw, KeycloakInstalled ki);
|
|
||||||
void failure(PrintWriter pw, KeycloakInstalled ki);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final String KEYCLOAK_JSON = "META-INF/keycloak.json";
|
private static final String KEYCLOAK_JSON = "META-INF/keycloak.json";
|
||||||
|
|
||||||
private KeycloakDeployment deployment;
|
private KeycloakDeployment deployment;
|
||||||
@@ -67,18 +59,12 @@ public class KeycloakInstalled {
|
|||||||
LOGGED_MANUAL, LOGGED_DESKTOP
|
LOGGED_MANUAL, LOGGED_DESKTOP
|
||||||
}
|
}
|
||||||
|
|
||||||
private AccessTokenResponse tokenResponse;
|
|
||||||
private String tokenString;
|
private String tokenString;
|
||||||
private String idTokenString;
|
private String idTokenString;
|
||||||
private IDToken idToken;
|
private IDToken idToken;
|
||||||
private AccessToken token;
|
private AccessToken token;
|
||||||
private String refreshToken;
|
private String refreshToken;
|
||||||
private Status status;
|
private Status status;
|
||||||
private Locale locale;
|
|
||||||
private HttpResponseWriter loginResponseWriter;
|
|
||||||
private HttpResponseWriter logoutResponseWriter;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public KeycloakInstalled() {
|
public KeycloakInstalled() {
|
||||||
InputStream config = Thread.currentThread().getContextClassLoader().getResourceAsStream(KEYCLOAK_JSON);
|
InputStream config = Thread.currentThread().getContextClassLoader().getResourceAsStream(KEYCLOAK_JSON);
|
||||||
@@ -89,92 +75,6 @@ public class KeycloakInstalled {
|
|||||||
deployment = KeycloakDeploymentBuilder.build(config);
|
deployment = KeycloakDeploymentBuilder.build(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeycloakInstalled(KeycloakDeployment deployment) {
|
|
||||||
this.deployment = deployment;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static HttpResponseWriter defaultLoginWriter = new HttpResponseWriter() {
|
|
||||||
@Override
|
|
||||||
public void success(PrintWriter pw, KeycloakInstalled ki) {
|
|
||||||
pw.println("HTTP/1.1 200 OK");
|
|
||||||
pw.println("Content-Type: text/html");
|
|
||||||
pw.println();
|
|
||||||
pw.println("<html><h1>Login completed.</h1><div>");
|
|
||||||
pw.println("This browser will remain logged in until you close it, logout, or the session expires.");
|
|
||||||
pw.println("</div></html>");
|
|
||||||
pw.flush();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void failure(PrintWriter pw, KeycloakInstalled ki) {
|
|
||||||
pw.println("HTTP/1.1 200 OK");
|
|
||||||
pw.println("Content-Type: text/html");
|
|
||||||
pw.println();
|
|
||||||
pw.println("<html><h1>Login attempt failed.</h1><div>");
|
|
||||||
pw.println("</div></html>");
|
|
||||||
pw.flush();
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
private static HttpResponseWriter defaultLogoutWriter = new HttpResponseWriter() {
|
|
||||||
@Override
|
|
||||||
public void success(PrintWriter pw, KeycloakInstalled ki) {
|
|
||||||
pw.println("HTTP/1.1 200 OK");
|
|
||||||
pw.println("Content-Type: text/html");
|
|
||||||
pw.println();
|
|
||||||
pw.println("<html><h1>Logout completed.</h1><div>");
|
|
||||||
pw.println("You may close this browser tab.");
|
|
||||||
pw.println("</div></html>");
|
|
||||||
pw.flush();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void failure(PrintWriter pw, KeycloakInstalled ki) {
|
|
||||||
pw.println("HTTP/1.1 200 OK");
|
|
||||||
pw.println("Content-Type: text/html");
|
|
||||||
pw.println();
|
|
||||||
pw.println("<html><h1>Logout failed.</h1><div>");
|
|
||||||
pw.println("You may close this browser tab.");
|
|
||||||
pw.println("</div></html>");
|
|
||||||
pw.flush();
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public HttpResponseWriter getLoginResponseWriter() {
|
|
||||||
if (loginResponseWriter == null) {
|
|
||||||
return defaultLoginWriter;
|
|
||||||
} else {
|
|
||||||
return loginResponseWriter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpResponseWriter getLogoutResponseWriter() {
|
|
||||||
if (logoutResponseWriter == null) {
|
|
||||||
return defaultLogoutWriter;
|
|
||||||
} else {
|
|
||||||
return logoutResponseWriter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLoginResponseWriter(HttpResponseWriter loginResponseWriter) {
|
|
||||||
this.loginResponseWriter = loginResponseWriter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLogoutResponseWriter(HttpResponseWriter logoutResponseWriter) {
|
|
||||||
this.logoutResponseWriter = logoutResponseWriter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Locale getLocale() {
|
|
||||||
return locale;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLocale(Locale locale) {
|
|
||||||
this.locale = locale;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void login() throws IOException, ServerRequest.HttpFailure, VerificationException, InterruptedException, OAuthErrorException, URISyntaxException {
|
public void login() throws IOException, ServerRequest.HttpFailure, VerificationException, InterruptedException, OAuthErrorException, URISyntaxException {
|
||||||
if (isDesktopSupported()) {
|
if (isDesktopSupported()) {
|
||||||
loginDesktop();
|
loginDesktop();
|
||||||
@@ -208,22 +108,19 @@ public class KeycloakInstalled {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void loginDesktop() throws IOException, VerificationException, OAuthErrorException, URISyntaxException, ServerRequest.HttpFailure, InterruptedException {
|
public void loginDesktop() throws IOException, VerificationException, OAuthErrorException, URISyntaxException, ServerRequest.HttpFailure, InterruptedException {
|
||||||
CallbackListener callback = new CallbackListener(getLoginResponseWriter());
|
CallbackListener callback = new CallbackListener();
|
||||||
callback.start();
|
callback.start();
|
||||||
|
|
||||||
String redirectUri = "http://localhost:" + callback.server.getLocalPort();
|
String redirectUri = "http://localhost:" + callback.server.getLocalPort();
|
||||||
String state = UUID.randomUUID().toString();
|
String state = UUID.randomUUID().toString();
|
||||||
|
|
||||||
KeycloakUriBuilder builder = deployment.getAuthUrl().clone()
|
String authUrl = 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, redirectUri)
|
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
|
||||||
.queryParam(OAuth2Constants.STATE, state)
|
.queryParam(OAuth2Constants.STATE, state)
|
||||||
.queryParam(OAuth2Constants.SCOPE, OAuth2Constants.SCOPE_OPENID);
|
.queryParam(OAuth2Constants.SCOPE, OAuth2Constants.SCOPE_OPENID)
|
||||||
if (locale != null) {
|
.build().toString();
|
||||||
builder.queryParam(OAuth2Constants.UI_LOCALES_PARAM, locale.getLanguage());
|
|
||||||
}
|
|
||||||
String authUrl = builder.build().toString();
|
|
||||||
|
|
||||||
Desktop.getDesktop().browse(new URI(authUrl));
|
Desktop.getDesktop().browse(new URI(authUrl));
|
||||||
|
|
||||||
@@ -247,7 +144,7 @@ public class KeycloakInstalled {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void logoutDesktop() throws IOException, URISyntaxException, InterruptedException {
|
private void logoutDesktop() throws IOException, URISyntaxException, InterruptedException {
|
||||||
CallbackListener callback = new CallbackListener(getLogoutResponseWriter());
|
CallbackListener callback = new CallbackListener();
|
||||||
callback.start();
|
callback.start();
|
||||||
|
|
||||||
String redirectUri = "http://localhost:" + callback.server.getLocalPort();
|
String redirectUri = "http://localhost:" + callback.server.getLocalPort();
|
||||||
@@ -270,6 +167,9 @@ public class KeycloakInstalled {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void loginManual(PrintStream printer, Reader reader) throws IOException, ServerRequest.HttpFailure, VerificationException {
|
public void loginManual(PrintStream printer, Reader reader) throws IOException, ServerRequest.HttpFailure, VerificationException {
|
||||||
|
CallbackListener callback = new CallbackListener();
|
||||||
|
callback.start();
|
||||||
|
|
||||||
String redirectUri = "urn:ietf:wg:oauth:2.0:oob";
|
String redirectUri = "urn:ietf:wg:oauth:2.0:oob";
|
||||||
|
|
||||||
String authUrl = deployment.getAuthUrl().clone()
|
String authUrl = deployment.getAuthUrl().clone()
|
||||||
@@ -308,14 +208,7 @@ public class KeycloakInstalled {
|
|||||||
parseAccessToken(tokenResponse);
|
parseAccessToken(tokenResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refreshToken(String refreshToken) throws IOException, ServerRequest.HttpFailure, VerificationException {
|
|
||||||
AccessTokenResponse tokenResponse = ServerRequest.invokeRefresh(deployment, refreshToken);
|
|
||||||
parseAccessToken(tokenResponse);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parseAccessToken(AccessTokenResponse tokenResponse) throws VerificationException {
|
private void parseAccessToken(AccessTokenResponse tokenResponse) throws VerificationException {
|
||||||
this.tokenResponse = tokenResponse;
|
|
||||||
tokenString = tokenResponse.getToken();
|
tokenString = tokenResponse.getToken();
|
||||||
refreshToken = tokenResponse.getRefreshToken();
|
refreshToken = tokenResponse.getRefreshToken();
|
||||||
idTokenString = tokenResponse.getIdToken();
|
idTokenString = tokenResponse.getIdToken();
|
||||||
@@ -347,10 +240,6 @@ public class KeycloakInstalled {
|
|||||||
return refreshToken;
|
return refreshToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccessTokenResponse getTokenResponse() {
|
|
||||||
return tokenResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDesktopSupported() {
|
public boolean isDesktopSupported() {
|
||||||
return Desktop.isDesktopSupported();
|
return Desktop.isDesktopSupported();
|
||||||
}
|
}
|
||||||
@@ -359,8 +248,6 @@ public class KeycloakInstalled {
|
|||||||
return deployment;
|
return deployment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void processCode(String code, String redirectUri) throws IOException, ServerRequest.HttpFailure, VerificationException {
|
private void processCode(String code, String redirectUri) throws IOException, ServerRequest.HttpFailure, VerificationException {
|
||||||
AccessTokenResponse tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, redirectUri, null);
|
AccessTokenResponse tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, redirectUri, null);
|
||||||
parseAccessToken(tokenResponse);
|
parseAccessToken(tokenResponse);
|
||||||
@@ -382,7 +269,6 @@ public class KeycloakInstalled {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class CallbackListener extends Thread {
|
public class CallbackListener extends Thread {
|
||||||
|
|
||||||
private ServerSocket server;
|
private ServerSocket server;
|
||||||
@@ -397,19 +283,14 @@ public class KeycloakInstalled {
|
|||||||
|
|
||||||
private String state;
|
private String state;
|
||||||
|
|
||||||
private Socket socket;
|
public CallbackListener() throws IOException {
|
||||||
|
|
||||||
private HttpResponseWriter writer;
|
|
||||||
|
|
||||||
public CallbackListener(HttpResponseWriter writer) throws IOException {
|
|
||||||
this.writer = writer;
|
|
||||||
server = new ServerSocket(0);
|
server = new ServerSocket(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
socket = server.accept();
|
Socket socket = server.accept();
|
||||||
|
|
||||||
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||||
String request = br.readLine();
|
String request = br.readLine();
|
||||||
@@ -433,15 +314,10 @@ public class KeycloakInstalled {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OutputStreamWriter out = new OutputStreamWriter(socket.getOutputStream());
|
PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
|
||||||
PrintWriter pw = new PrintWriter(out);
|
pw.println("Please close window and return to application");
|
||||||
|
|
||||||
if (error == null) {
|
|
||||||
writer.success(pw, KeycloakInstalled.this);
|
|
||||||
} else {
|
|
||||||
writer.failure(pw, KeycloakInstalled.this);
|
|
||||||
}
|
|
||||||
pw.flush();
|
pw.flush();
|
||||||
|
|
||||||
socket.close();
|
socket.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
errorException = e;
|
errorException = e;
|
||||||
@@ -452,8 +328,6 @@ public class KeycloakInstalled {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>3.3.0.CR1</version>
|
<version>2.5.4.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>3.3.0.CR1</version>
|
<version>2.5.4.Final</version>
|
||||||
<relativePath>../../../../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+1
-3
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>3.3.0.CR1</version>
|
<version>2.5.4.Final</version>
|
||||||
<relativePath>../../../../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
-40
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-6
@@ -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();
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>3.3.0.CR1</version>
|
<version>2.5.4.Final</version>
|
||||||
<relativePath>../../../../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
-40
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-6
@@ -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();
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>3.3.0.CR1</version>
|
<version>2.5.4.Final</version>
|
||||||
<relativePath>../../../../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
-40
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-6
@@ -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) {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>3.3.0.CR1</version>
|
<version>2.5.4.Final</version>
|
||||||
<relativePath>../../../../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
-6
@@ -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) {
|
||||||
|
|||||||
@@ -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.3.0.CR1</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>
|
|
||||||
-47
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-40
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-111
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
-75
@@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>3.3.0.CR1</version>
|
<version>2.5.4.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>
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>3.3.0.CR1</version>
|
<version>2.5.4.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@@ -38,11 +38,6 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>com.samaxes.maven</groupId>
|
<groupId>com.samaxes.maven</groupId>
|
||||||
<artifactId>minify-maven-plugin</artifactId>
|
<artifactId>minify-maven-plugin</artifactId>
|
||||||
<configuration>
|
|
||||||
<jsEngine>CLOSURE</jsEngine>
|
|
||||||
<closureLanguageIn>ECMASCRIPT5</closureLanguageIn>
|
|
||||||
<closureCreateSourceMap>true</closureCreateSourceMap>
|
|
||||||
</configuration>
|
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>min-js</id>
|
<id>min-js</id>
|
||||||
|
|||||||
@@ -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
@@ -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>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -33,13 +33,6 @@
|
|||||||
interval: 5
|
interval: 5
|
||||||
};
|
};
|
||||||
|
|
||||||
var scripts = document.getElementsByTagName('script');
|
|
||||||
for (var i = 0; i < scripts.length; i++) {
|
|
||||||
if ((scripts[i].src.indexOf('keycloak.js') !== -1 || scripts[i].src.indexOf('keycloak.min.js') !== -1) && scripts[i].src.indexOf('version=') !== -1) {
|
|
||||||
kc.iframeVersion = scripts[i].src.substring(scripts[i].src.indexOf('version=') + 8).split('&')[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kc.init = function (initOptions) {
|
kc.init = function (initOptions) {
|
||||||
kc.authenticated = false;
|
kc.authenticated = false;
|
||||||
|
|
||||||
@@ -164,7 +157,7 @@
|
|||||||
processCallback(callback, initPromise);
|
processCallback(callback, initPromise);
|
||||||
return;
|
return;
|
||||||
} else if (initOptions) {
|
} else if (initOptions) {
|
||||||
if (initOptions.token && initOptions.refreshToken) {
|
if (initOptions.refreshToken) {
|
||||||
setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken);
|
setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken);
|
||||||
|
|
||||||
if (loginIframe.enable) {
|
if (loginIframe.enable) {
|
||||||
@@ -221,7 +214,7 @@
|
|||||||
var callbackState = {
|
var callbackState = {
|
||||||
state: state,
|
state: state,
|
||||||
nonce: nonce,
|
nonce: nonce,
|
||||||
redirectUri: encodeURIComponent(redirectUri)
|
redirectUri: encodeURIComponent(redirectUri),
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options && options.prompt) {
|
if (options && options.prompt) {
|
||||||
@@ -594,7 +587,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'];
|
||||||
@@ -640,10 +633,6 @@
|
|||||||
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);
|
||||||
@@ -838,26 +827,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var src = getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html';
|
var src = getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html';
|
||||||
if (kc.iframeVersion) {
|
|
||||||
src = src + '?version=' + kc.iframeVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
iframe.setAttribute('src', src );
|
iframe.setAttribute('src', src );
|
||||||
iframe.setAttribute('title', 'keycloak-session-iframe' );
|
|
||||||
iframe.style.display = 'none';
|
iframe.style.display = 'none';
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -865,7 +844,7 @@
|
|||||||
|
|
||||||
for (var i = callbacks.length - 1; i >= 0; --i) {
|
for (var i = callbacks.length - 1; i >= 0; --i) {
|
||||||
var promise = callbacks[i];
|
var promise = callbacks[i];
|
||||||
if (event.data == 'unchanged') {
|
if (event.data == "unchanged") {
|
||||||
promise.setSuccess();
|
promise.setSuccess();
|
||||||
} else {
|
} else {
|
||||||
promise.setError();
|
promise.setError();
|
||||||
@@ -1244,7 +1223,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
~ and other contributors as indicated by the @author tags.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function getCookie(cname)
|
||||||
|
{
|
||||||
|
var name = cname + "=";
|
||||||
|
var ca = document.cookie.split(';');
|
||||||
|
for(var i=0; i<ca.length; i++)
|
||||||
|
{
|
||||||
|
var c = ca[i].trim();
|
||||||
|
if (c.indexOf(name)==0) return c.substring(name.length,c.length);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
function receiveMessage(event)
|
||||||
|
{
|
||||||
|
if (event.origin !== "ORIGIN") {
|
||||||
|
console.log(event.origin + " does not match built origin");
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
var data = JSON.parse(event.data);
|
||||||
|
data.loggedIn = false;
|
||||||
|
var cookie = getCookie('KEYCLOAK_SESSION');
|
||||||
|
if (cookie) {
|
||||||
|
data.loggedIn = true;
|
||||||
|
data.session = cookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.source.postMessage(JSON.stringify(data),
|
||||||
|
event.origin);
|
||||||
|
}
|
||||||
|
window.addEventListener("message", receiveMessage, false);
|
||||||
|
</script>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
} else if (!init) {
|
} else if (!init) {
|
||||||
var req = new XMLHttpRequest();
|
var req = new XMLHttpRequest();
|
||||||
|
|
||||||
var url = location.href.split("?")[0] + "/init";
|
var url = location.href + "/init";
|
||||||
url += "?client_id=" + encodeURIComponent(clientId);
|
url += "?client_id=" + encodeURIComponent(clientId);
|
||||||
url += "&origin=" + encodeURIComponent(origin);
|
url += "&origin=" + encodeURIComponent(origin);
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>3.3.0.CR1</version>
|
<version>2.5.4.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>3.3.0.CR1</version>
|
<version>2.5.4.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>
|
||||||
@@ -34,7 +34,6 @@
|
|||||||
<module>adapter-core</module>
|
<module>adapter-core</module>
|
||||||
<module>as7-eap6</module>
|
<module>as7-eap6</module>
|
||||||
<module>installed</module>
|
<module>installed</module>
|
||||||
<module>cli-sso</module>
|
|
||||||
<module>jaxrs-oauth-client</module>
|
<module>jaxrs-oauth-client</module>
|
||||||
<module>jetty</module>
|
<module>jetty</module>
|
||||||
<module>js</module>
|
<module>js</module>
|
||||||
@@ -42,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>
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>3.3.0.CR1</version>
|
<version>2.5.4.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+31
-58
@@ -53,97 +53,70 @@ 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);
|
if (configResolverClass != null) {
|
||||||
log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", definedconfigResolver.getClass());
|
try {
|
||||||
|
KeycloakConfigResolver configResolver = (KeycloakConfigResolver) getClass().getClassLoader().loadClass(configResolverClass).newInstance();
|
||||||
|
deploymentContext = new AdapterDeploymentContext(configResolver);
|
||||||
|
log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.log(Level.FINE, "The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", new Object[]{configResolverClass, ex.getMessage()});
|
||||||
|
deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
String configResolverClass = filterConfig.getInitParameter(CONFIG_RESOLVER_PARAM);
|
String fp = filterConfig.getInitParameter("keycloak.config.file");
|
||||||
if (configResolverClass != null) {
|
InputStream is = null;
|
||||||
|
if (fp != null) {
|
||||||
try {
|
try {
|
||||||
KeycloakConfigResolver configResolver = (KeycloakConfigResolver) getClass().getClassLoader().loadClass(configResolverClass).newInstance();
|
is = new FileInputStream(fp);
|
||||||
deploymentContext = new AdapterDeploymentContext(configResolver);
|
} catch (FileNotFoundException e) {
|
||||||
log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass);
|
throw new RuntimeException(e);
|
||||||
} catch (Exception ex) {
|
|
||||||
log.log(Level.FINE, "The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", new Object[]{configResolverClass, ex.getMessage()});
|
|
||||||
deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
String fp = filterConfig.getInitParameter(CONFIG_FILE_PARAM);
|
String path = "/WEB-INF/keycloak.json";
|
||||||
InputStream is = null;
|
String pathParam = filterConfig.getInitParameter("keycloak.config.path");
|
||||||
if (fp != null) {
|
if (pathParam != null) path = pathParam;
|
||||||
try {
|
is = filterConfig.getServletContext().getResourceAsStream(path);
|
||||||
is = new FileInputStream(fp);
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
String path = "/WEB-INF/keycloak.json";
|
|
||||||
String pathParam = filterConfig.getInitParameter(CONFIG_PATH_PARAM);
|
|
||||||
if (pathParam != null) path = pathParam;
|
|
||||||
is = filterConfig.getServletContext().getResourceAsStream(path);
|
|
||||||
}
|
|
||||||
KeycloakDeployment kd = createKeycloakDeploymentFrom(is);
|
|
||||||
deploymentContext = new AdapterDeploymentContext(kd);
|
|
||||||
log.fine("Keycloak is using a per-deployment configuration.");
|
|
||||||
}
|
}
|
||||||
|
KeycloakDeployment kd = createKeycloakDeploymentFrom(is);
|
||||||
|
deploymentContext = new AdapterDeploymentContext(kd);
|
||||||
|
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;
|
||||||
|
|
||||||
@@ -225,7 +198,7 @@ public class KeycloakOIDCFilter implements Filter {
|
|||||||
*
|
*
|
||||||
* @param request the request to check
|
* @param request the request to check
|
||||||
* @return {@code true} if the request should not be handled,
|
* @return {@code true} if the request should not be handled,
|
||||||
* {@code false} otherwise.
|
* {@code false} otherwise.
|
||||||
*/
|
*/
|
||||||
private boolean shouldSkip(HttpServletRequest request) {
|
private boolean shouldSkip(HttpServletRequest request) {
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>3.3.0.CR1</version>
|
<version>2.5.4.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+1
-63
@@ -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,15 +57,7 @@ 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);
|
||||||
|
return ServerRequest.invokeAccessCodeToToken(resolvedDeployment, code, redirectUri, null);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -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())
|
||||||
|
|||||||
@@ -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.3.0.CR1</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>
|
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>3.3.0.CR1</version>
|
<version>2.5.4.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@@ -31,9 +31,7 @@
|
|||||||
<description/>
|
<description/>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<spring-boot.version>1.4.0.RELEASE</spring-boot.version>
|
<spring-boot.version>1.2.1.RELEASE</spring-boot.version>
|
||||||
<spring.version>4.1.6.RELEASE</spring.version>
|
|
||||||
<mockito.version>1.9.5</mockito.version>
|
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -45,79 +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</groupId>
|
|
||||||
<artifactId>spring-test</artifactId>
|
|
||||||
<version>${spring.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.mockito</groupId>
|
|
||||||
<artifactId>mockito-all</artifactId>
|
|
||||||
<version>${mockito.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</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>
|
||||||
@@ -125,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>
|
||||||
+37
-67
@@ -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,9 +153,8 @@ 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();
|
Constraint jettyConstraint = new Constraint();
|
||||||
|
|
||||||
if (constraintDefinition.getAuthRoles().size() > 0) {
|
|
||||||
jettyConstraint.setAuthenticate(true);
|
|
||||||
jettyConstraint.setRoles(constraintDefinition.getAuthRoles().toArray(new String[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
jettyConstraint.setName(securityCollectionDefinition.getName());
|
jettyConstraint.setName(securityCollectionDefinition.getName());
|
||||||
|
jettyConstraint.setAuthenticate(true);
|
||||||
|
|
||||||
// according to the servlet spec each security-constraint has at least one URL pattern
|
if (securityCollectionDefinition.getName() != null) {
|
||||||
for(String pattern : securityCollectionDefinition.getPatterns()) {
|
jettyConstraint.setName(securityCollectionDefinition.getName());
|
||||||
|
|
||||||
/* the following code is asymmetric as Jetty's ConstraintMapping accepts only one allowed HTTP method,
|
|
||||||
but multiple omitted methods. Therefore we add one ConstraintMapping for each allowed
|
|
||||||
mapping but only one mapping in the cases of omitted methods or no methods.
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (securityCollectionDefinition.getMethods().size() > 0) {
|
|
||||||
// according to the servlet spec we have either methods ...
|
|
||||||
for(String method : securityCollectionDefinition.getMethods()) {
|
|
||||||
ConstraintMapping jettyConstraintMapping = new ConstraintMapping();
|
|
||||||
jettyConstraintMappings.add(jettyConstraintMapping);
|
|
||||||
|
|
||||||
jettyConstraintMapping.setConstraint(jettyConstraint);
|
|
||||||
jettyConstraintMapping.setPathSpec(pattern);
|
|
||||||
jettyConstraintMapping.setMethod(method);
|
|
||||||
}
|
|
||||||
} else if (securityCollectionDefinition.getOmittedMethods().size() > 0){
|
|
||||||
// ... omitted methods ...
|
|
||||||
ConstraintMapping jettyConstraintMapping = new ConstraintMapping();
|
|
||||||
jettyConstraintMappings.add(jettyConstraintMapping);
|
|
||||||
|
|
||||||
jettyConstraintMapping.setConstraint(jettyConstraint);
|
|
||||||
jettyConstraintMapping.setPathSpec(pattern);
|
|
||||||
jettyConstraintMapping.setMethodOmissions(
|
|
||||||
securityCollectionDefinition.getOmittedMethods().toArray(new String[0]));
|
|
||||||
} else {
|
|
||||||
// ... or no methods at all
|
|
||||||
ConstraintMapping jettyConstraintMapping = new ConstraintMapping();
|
|
||||||
jettyConstraintMappings.add(jettyConstraintMapping);
|
|
||||||
|
|
||||||
jettyConstraintMapping.setConstraint(jettyConstraint);
|
|
||||||
jettyConstraintMapping.setPathSpec(pattern);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jettyConstraint.setRoles(securityCollectionDefinition.getAuthRoles().toArray(new String[0]));
|
||||||
|
|
||||||
|
ConstraintMapping jettyConstraintMapping = new ConstraintMapping();
|
||||||
|
if (securityCollectionDefinition.getPatterns().size() > 0) {
|
||||||
|
//First pattern wins
|
||||||
|
jettyConstraintMapping.setPathSpec(securityCollectionDefinition.getPatterns().get(0));
|
||||||
|
jettyConstraintMapping.setConstraint(jettyConstraint);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (securityCollectionDefinition.getMethods().size() > 0) {
|
||||||
|
//First method wins
|
||||||
|
jettyConstraintMapping.setMethod(securityCollectionDefinition.getMethods().get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
jettyConstraintMapping.setMethodOmissions(
|
||||||
|
securityCollectionDefinition.getOmittedMethods().toArray(new String[0]));
|
||||||
|
|
||||||
|
jettyConstraintMappings.add(jettyConstraintMapping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WebAppContext webAppContext = server.getBean(WebAppContext.class);
|
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,10 +235,12 @@ 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()) {
|
||||||
if (!authRoles.contains(authRole)) {
|
for (String authRole : collection.getAuthRoles()) {
|
||||||
context.addSecurityRole(authRole);
|
if (!authRoles.contains(authRole)) {
|
||||||
authRoles.add(authRole);
|
context.addSecurityRole(authRole);
|
||||||
|
authRoles.add(authRole);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -278,10 +248,6 @@ public class KeycloakAutoConfiguration {
|
|||||||
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);
|
||||||
}
|
}
|
||||||
+9
-52
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
-24
@@ -1,24 +0,0 @@
|
|||||||
package org.keycloak.adapters.springboot.client;
|
|
||||||
|
|
||||||
import org.springframework.boot.web.client.RestTemplateCustomizer;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
|
|
||||||
public class KeycloakRestTemplateCustomizer implements RestTemplateCustomizer {
|
|
||||||
|
|
||||||
private final KeycloakSecurityContextClientRequestInterceptor keycloakInterceptor;
|
|
||||||
|
|
||||||
public KeycloakRestTemplateCustomizer() {
|
|
||||||
this(new KeycloakSecurityContextClientRequestInterceptor());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected KeycloakRestTemplateCustomizer(
|
|
||||||
KeycloakSecurityContextClientRequestInterceptor keycloakInterceptor
|
|
||||||
) {
|
|
||||||
this.keycloakInterceptor = keycloakInterceptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void customize(RestTemplate restTemplate) {
|
|
||||||
restTemplate.getInterceptors().add(keycloakInterceptor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-55
@@ -1,55 +0,0 @@
|
|||||||
package org.keycloak.adapters.springboot.client;
|
|
||||||
|
|
||||||
import org.keycloak.KeycloakPrincipal;
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
|
||||||
import org.springframework.http.HttpRequest;
|
|
||||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
|
||||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
|
||||||
import org.springframework.http.client.ClientHttpResponse;
|
|
||||||
import org.springframework.web.context.request.RequestContextHolder;
|
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.Principal;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interceptor for {@link ClientHttpRequestExecution} objects created for server to server secured
|
|
||||||
* communication using OAuth2 bearer tokens issued by Keycloak.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:jmcshan1@gmail.com">James McShane</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class KeycloakSecurityContextClientRequestInterceptor implements ClientHttpRequestInterceptor {
|
|
||||||
|
|
||||||
private static final String AUTHORIZATION_HEADER = "Authorization";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link KeycloakSecurityContext} from the Spring {@link ServletRequestAttributes}'s {@link Principal}.
|
|
||||||
*
|
|
||||||
* The principal must support retrieval of the KeycloakSecurityContext, so at this point, only {@link KeycloakPrincipal}
|
|
||||||
* values are supported
|
|
||||||
*
|
|
||||||
* @return the current <code>KeycloakSecurityContext</code>
|
|
||||||
*/
|
|
||||||
protected KeycloakSecurityContext getKeycloakSecurityContext() {
|
|
||||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
|
||||||
Principal principal = attributes.getRequest().getUserPrincipal();
|
|
||||||
if (principal == null) {
|
|
||||||
throw new IllegalStateException("Cannot set authorization header because there is no authenticated principal");
|
|
||||||
}
|
|
||||||
if (!(principal instanceof KeycloakPrincipal)) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
String.format(
|
|
||||||
"Cannot set authorization header because the principal type %s does not provide the KeycloakSecurityContext",
|
|
||||||
principal.getClass()));
|
|
||||||
}
|
|
||||||
return ((KeycloakPrincipal) principal).getKeycloakSecurityContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
|
|
||||||
KeycloakSecurityContext context = this.getKeycloakSecurityContext();
|
|
||||||
httpRequest.getHeaders().set(AUTHORIZATION_HEADER, "Bearer " + context.getTokenString());
|
|
||||||
return clientHttpRequestExecution.execute(httpRequest, bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||||
org.keycloak.adapters.springboot.KeycloakAutoConfiguration
|
org.keycloak.adapters.springboot.KeycloakSpringBootConfiguration
|
||||||
-28
@@ -1,28 +0,0 @@
|
|||||||
package org.keycloak.adapters.springboot.client;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
|
|
||||||
public class KeycloakRestTemplateCustomizerTest {
|
|
||||||
|
|
||||||
private KeycloakRestTemplateCustomizer customizer;
|
|
||||||
private KeycloakSecurityContextClientRequestInterceptor interceptor =
|
|
||||||
mock(KeycloakSecurityContextClientRequestInterceptor.class);
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() {
|
|
||||||
customizer = new KeycloakRestTemplateCustomizer(interceptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void interceptorIsAddedToRequest() {
|
|
||||||
RestTemplate restTemplate = new RestTemplate();
|
|
||||||
customizer.customize(restTemplate);
|
|
||||||
assertTrue(restTemplate.getInterceptors().contains(interceptor));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
-87
@@ -1,87 +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.springboot.client;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.KeycloakPrincipal;
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
import org.mockito.Spy;
|
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
|
||||||
import org.springframework.web.context.request.RequestContextHolder;
|
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
|
||||||
|
|
||||||
import java.security.Principal;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Keycloak spring boot client request factory tests.
|
|
||||||
*/
|
|
||||||
public class KeycloakSecurityContextClientRequestInterceptorTest {
|
|
||||||
|
|
||||||
@Spy
|
|
||||||
private KeycloakSecurityContextClientRequestInterceptor factory;
|
|
||||||
|
|
||||||
private MockHttpServletRequest servletRequest;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private KeycloakSecurityContext keycloakSecurityContext;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private KeycloakPrincipal keycloakPrincipal;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
servletRequest = new MockHttpServletRequest();
|
|
||||||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(servletRequest));
|
|
||||||
servletRequest.setUserPrincipal(keycloakPrincipal);
|
|
||||||
when(keycloakPrincipal.getKeycloakSecurityContext()).thenReturn(keycloakSecurityContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetKeycloakSecurityContext() throws Exception {
|
|
||||||
KeycloakSecurityContext context = factory.getKeycloakSecurityContext();
|
|
||||||
assertNotNull(context);
|
|
||||||
assertEquals(keycloakSecurityContext, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IllegalStateException.class)
|
|
||||||
public void testGetKeycloakSecurityContextInvalidPrincipal() throws Exception {
|
|
||||||
servletRequest.setUserPrincipal(new MarkerPrincipal());
|
|
||||||
factory.getKeycloakSecurityContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IllegalStateException.class)
|
|
||||||
public void testGetKeycloakSecurityContextNullAuthentication() throws Exception {
|
|
||||||
servletRequest.setUserPrincipal(null);
|
|
||||||
factory.getKeycloakSecurityContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class MarkerPrincipal implements Principal {
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>3.3.0.CR1</version>
|
<version>2.5.4.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>
|
||||||
|
|||||||
-25
@@ -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 {
|
|
||||||
}
|
|
||||||
-41
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-5
@@ -19,9 +19,6 @@ package org.keycloak.adapters.springsecurity.facade;
|
|||||||
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
import org.keycloak.KeycloakSecurityContext;
|
||||||
import org.keycloak.adapters.OIDCHttpFacade;
|
import org.keycloak.adapters.OIDCHttpFacade;
|
||||||
import org.keycloak.adapters.spi.KeycloakAccount;
|
|
||||||
import org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount;
|
|
||||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
@@ -60,8 +57,7 @@ public class SimpleHttpFacade implements OIDCHttpFacade {
|
|||||||
SecurityContext context = SecurityContextHolder.getContext();
|
SecurityContext context = SecurityContextHolder.getContext();
|
||||||
|
|
||||||
if (context != null && context.getAuthentication() != null) {
|
if (context != null && context.getAuthentication() != null) {
|
||||||
KeycloakAuthenticationToken authentication = (KeycloakAuthenticationToken) context.getAuthentication();
|
return (KeycloakSecurityContext) context.getAuthentication().getDetails();
|
||||||
return authentication.getAccount().getKeycloakSecurityContext();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
+27
-35
@@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -111,7 +109,7 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public KeycloakAuthenticationProcessingFilter(AuthenticationManager authenticationManager, RequestMatcher
|
public KeycloakAuthenticationProcessingFilter(AuthenticationManager authenticationManager, RequestMatcher
|
||||||
requiresAuthenticationRequestMatcher) {
|
requiresAuthenticationRequestMatcher) {
|
||||||
super(requiresAuthenticationRequestMatcher);
|
super(requiresAuthenticationRequestMatcher);
|
||||||
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
|
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
|
||||||
this.authenticationManager = authenticationManager;
|
this.authenticationManager = authenticationManager;
|
||||||
@@ -142,27 +140,9 @@ 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");
|
||||||
@@ -204,7 +184,7 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
|
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
|
||||||
Authentication authResult) throws IOException, ServletException {
|
Authentication authResult) throws IOException, ServletException {
|
||||||
|
|
||||||
if (!(this.isBearerTokenRequest(request) || this.isBasicAuthRequest(request))) {
|
if (!(this.isBearerTokenRequest(request) || this.isBasicAuthRequest(request))) {
|
||||||
super.successfulAuthentication(request, response, chain, authResult);
|
super.successfulAuthentication(request, response, chain, authResult);
|
||||||
@@ -232,7 +212,19 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,4 +262,4 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
|
|||||||
public final void setContinueChainBeforeSuccessfulAuthentication(boolean continueChainBeforeSuccessfulAuthentication) {
|
public final void setContinueChainBeforeSuccessfulAuthentication(boolean continueChainBeforeSuccessfulAuthentication) {
|
||||||
throw new UnsupportedOperationException("This filter does not support explicitly setting a continue chain before success policy");
|
throw new UnsupportedOperationException("This filter does not support explicitly setting a continue chain before success policy");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
-41
@@ -1,41 +0,0 @@
|
|||||||
package org.keycloak.adapters.springsecurity.facade;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.spi.KeycloakAccount;
|
|
||||||
import org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount;
|
|
||||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
|
||||||
import org.mockito.internal.util.collections.Sets;
|
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
|
|
||||||
public class SimpleHttpFacadeTest {
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() {
|
|
||||||
SecurityContext springSecurityContext = SecurityContextHolder.createEmptyContext();
|
|
||||||
SecurityContextHolder.setContext(springSecurityContext);
|
|
||||||
Set<String> roles = Sets.newSet("user");
|
|
||||||
Principal principal = mock(Principal.class);
|
|
||||||
RefreshableKeycloakSecurityContext keycloakSecurityContext = mock(RefreshableKeycloakSecurityContext.class);
|
|
||||||
KeycloakAccount account = new SimpleKeycloakAccount(principal, roles, keycloakSecurityContext);
|
|
||||||
KeycloakAuthenticationToken token = new KeycloakAuthenticationToken(account);
|
|
||||||
springSecurityContext.setAuthentication(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldRetrieveKeycloakSecurityContext() {
|
|
||||||
SimpleHttpFacade facade = new SimpleHttpFacade(new MockHttpServletRequest(), new MockHttpServletResponse());
|
|
||||||
|
|
||||||
assertNotNull(facade.getSecurityContext());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+5
-23
@@ -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;
|
||||||
@@ -90,8 +89,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);
|
||||||
@@ -159,8 +155,8 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
|||||||
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);
|
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 +167,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 +210,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,18 +220,10 @@ 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 {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>3.3.0.CR1</version>
|
<version>2.5.4.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>tomcat6</module>
|
||||||
|
<module>tomcat7</module>
|
||||||
<module>tomcat8</module>
|
<module>tomcat8</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<profiles>
|
|
||||||
<profile>
|
|
||||||
<id>community</id>
|
|
||||||
<activation>
|
|
||||||
<property>
|
|
||||||
<name>!product</name>
|
|
||||||
</property>
|
|
||||||
</activation>
|
|
||||||
<modules>
|
|
||||||
<module>tomcat6</module>
|
|
||||||
<module>tomcat7</module>
|
|
||||||
</modules>
|
|
||||||
</profile>
|
|
||||||
</profiles>
|
|
||||||
</project>
|
</project>
|
||||||
@@ -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.3.0.CR1</version>
|
<version>2.5.4.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 />
|
||||||
|
|||||||
@@ -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.3.0.CR1</version>
|
<version>2.5.4.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -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.3.0.CR1</version>
|
<version>2.5.4.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -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.3.0.CR1</version>
|
<version>2.5.4.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>3.3.0.CR1</version>
|
<version>2.5.4.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -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.3.0.CR1</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>
|
|
||||||
-103
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-164
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-413
@@ -1,413 +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.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
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;
|
|
||||||
private final Map<String, String> headers = new HashMap<>();
|
|
||||||
|
|
||||||
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) {
|
|
||||||
HttpScope requestScope = request.getScope(Scope.EXCHANGE);
|
|
||||||
RefreshableKeycloakSecurityContext keycloakSecurityContext = account.getKeycloakSecurityContext();
|
|
||||||
|
|
||||||
requestScope.setAttachment(KeycloakSecurityContext.class.getName(), keycloakSecurityContext);
|
|
||||||
|
|
||||||
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) {
|
|
||||||
headers.put(name, value);
|
|
||||||
responseConsumer = responseConsumer.andThen(new Consumer<HttpServerResponse>() {
|
|
||||||
@Override
|
|
||||||
public void accept(HttpServerResponse response) {
|
|
||||||
String latestValue = headers.get(name);
|
|
||||||
|
|
||||||
if (latestValue.equals(value)) {
|
|
||||||
response.addResponseHeader(name, latestValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-86
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-202
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-26
@@ -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);
|
|
||||||
}
|
|
||||||
-109
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-168
@@ -1,168 +0,0 @@
|
|||||||
/*
|
|
||||||
* JBoss, Home of Professional Open Source.
|
|
||||||
* Copyright 2016 Red Hat, Inc., and individual contributors
|
|
||||||
* as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.adapters.elytron;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.security.auth.callback.CallbackHandler;
|
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
|
||||||
import org.keycloak.adapters.AuthenticatedActionsHandler;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.NodesRegistrationManagement;
|
|
||||||
import org.keycloak.adapters.PreAuthActionsHandler;
|
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.RequestAuthenticator;
|
|
||||||
import org.keycloak.adapters.spi.AuthChallenge;
|
|
||||||
import org.keycloak.adapters.spi.AuthOutcome;
|
|
||||||
import org.keycloak.adapters.spi.UserSessionManagement;
|
|
||||||
import org.wildfly.security.http.HttpAuthenticationException;
|
|
||||||
import org.wildfly.security.http.HttpScope;
|
|
||||||
import org.wildfly.security.http.HttpServerAuthenticationMechanism;
|
|
||||||
import org.wildfly.security.http.HttpServerRequest;
|
|
||||||
import org.wildfly.security.http.Scope;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
|
||||||
*/
|
|
||||||
class KeycloakHttpServerAuthenticationMechanism implements HttpServerAuthenticationMechanism {
|
|
||||||
|
|
||||||
static Logger LOGGER = Logger.getLogger(KeycloakHttpServerAuthenticationMechanismFactory.class);
|
|
||||||
static final String NAME = "KEYCLOAK";
|
|
||||||
|
|
||||||
private final Map<String, ?> properties;
|
|
||||||
private final CallbackHandler callbackHandler;
|
|
||||||
private final AdapterDeploymentContext deploymentContext;
|
|
||||||
|
|
||||||
public KeycloakHttpServerAuthenticationMechanism(Map<String, ?> properties, CallbackHandler callbackHandler, AdapterDeploymentContext deploymentContext) {
|
|
||||||
this.properties = properties;
|
|
||||||
this.callbackHandler = callbackHandler;
|
|
||||||
this.deploymentContext = deploymentContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getMechanismName() {
|
|
||||||
return NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void evaluateRequest(HttpServerRequest request) throws HttpAuthenticationException {
|
|
||||||
LOGGER.debugf("Evaluating request for path [%s]", request.getRequestURI());
|
|
||||||
AdapterDeploymentContext deploymentContext = getDeploymentContext(request);
|
|
||||||
|
|
||||||
if (deploymentContext == null) {
|
|
||||||
LOGGER.debugf("Ignoring request for path [%s] from mechanism [%s]. No deployment context found.", request.getRequestURI(), getMechanismName());
|
|
||||||
request.noAuthenticationInProgress();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ElytronHttpFacade httpFacade = new ElytronHttpFacade(request, deploymentContext, callbackHandler);
|
|
||||||
KeycloakDeployment deployment = httpFacade.getDeployment();
|
|
||||||
|
|
||||||
if (!deployment.isConfigured()) {
|
|
||||||
request.noAuthenticationInProgress();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
RequestAuthenticator authenticator = createRequestAuthenticator(request, httpFacade, deployment);
|
|
||||||
|
|
||||||
httpFacade.getTokenStore().checkCurrentToken();
|
|
||||||
|
|
||||||
if (preActions(httpFacade, deploymentContext)) {
|
|
||||||
LOGGER.debugf("Pre-actions has aborted the evaluation of [%s]", request.getRequestURI());
|
|
||||||
httpFacade.authenticationInProgress();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthOutcome outcome = authenticator.authenticate();
|
|
||||||
|
|
||||||
if (AuthOutcome.AUTHENTICATED.equals(outcome)) {
|
|
||||||
if (new AuthenticatedActionsHandler(deployment, httpFacade).handledRequest()) {
|
|
||||||
httpFacade.authenticationInProgress();
|
|
||||||
} else {
|
|
||||||
httpFacade.authenticationComplete();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthChallenge challenge = authenticator.getChallenge();
|
|
||||||
|
|
||||||
if (challenge != null) {
|
|
||||||
httpFacade.noAuthenticationInProgress(challenge);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (AuthOutcome.FAILED.equals(outcome)) {
|
|
||||||
httpFacade.getResponse().setStatus(403);
|
|
||||||
httpFacade.authenticationFailed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
httpFacade.noAuthenticationInProgress();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ElytronRequestAuthenticator createRequestAuthenticator(HttpServerRequest request, ElytronHttpFacade httpFacade, KeycloakDeployment deployment) {
|
|
||||||
return new ElytronRequestAuthenticator(this.callbackHandler, httpFacade, deployment, getConfidentialPort(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
private AdapterDeploymentContext getDeploymentContext(HttpServerRequest request) {
|
|
||||||
if (this.deploymentContext == null) {
|
|
||||||
return (AdapterDeploymentContext) request.getScope(Scope.APPLICATION).getAttachment(AdapterDeploymentContext.class.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.deploymentContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean preActions(ElytronHttpFacade httpFacade, AdapterDeploymentContext deploymentContext) {
|
|
||||||
NodesRegistrationManagement nodesRegistrationManagement = new NodesRegistrationManagement();
|
|
||||||
|
|
||||||
nodesRegistrationManagement.tryRegister(httpFacade.getDeployment());
|
|
||||||
|
|
||||||
PreAuthActionsHandler preActions = new PreAuthActionsHandler(new UserSessionManagement() {
|
|
||||||
@Override
|
|
||||||
public void logoutAll() {
|
|
||||||
Collection<String> sessions = httpFacade.getScopeIds(Scope.SESSION);
|
|
||||||
logoutHttpSessions(new ArrayList<>(sessions));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logoutHttpSessions(List<String> ids) {
|
|
||||||
for (String id : ids) {
|
|
||||||
HttpScope session = httpFacade.getScope(Scope.SESSION, id);
|
|
||||||
|
|
||||||
if (session != null) {
|
|
||||||
session.invalidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}, deploymentContext, httpFacade);
|
|
||||||
|
|
||||||
return preActions.handleRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: obtain confidential port from Elytron
|
|
||||||
private int getConfidentialPort(HttpServerRequest request) {
|
|
||||||
return 8443;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-67
@@ -1,67 +0,0 @@
|
|||||||
/*
|
|
||||||
* JBoss, Home of Professional Open Source.
|
|
||||||
* Copyright 2016 Red Hat, Inc., and individual contributors
|
|
||||||
* as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.adapters.elytron;
|
|
||||||
|
|
||||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
|
||||||
import org.wildfly.security.http.HttpAuthenticationException;
|
|
||||||
import org.wildfly.security.http.HttpServerAuthenticationMechanism;
|
|
||||||
import org.wildfly.security.http.HttpServerAuthenticationMechanismFactory;
|
|
||||||
|
|
||||||
import javax.security.auth.callback.CallbackHandler;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
|
||||||
*/
|
|
||||||
public class KeycloakHttpServerAuthenticationMechanismFactory implements HttpServerAuthenticationMechanismFactory {
|
|
||||||
|
|
||||||
private final AdapterDeploymentContext deploymentContext;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Creates a new instance.
|
|
||||||
*
|
|
||||||
* <p>A default constructor is necessary in order to allow this factory to be loaded via {@link java.util.ServiceLoader}.
|
|
||||||
*/
|
|
||||||
public KeycloakHttpServerAuthenticationMechanismFactory() {
|
|
||||||
this(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeycloakHttpServerAuthenticationMechanismFactory(AdapterDeploymentContext deploymentContext) {
|
|
||||||
this.deploymentContext = deploymentContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getMechanismNames(Map<String, ?> properties) {
|
|
||||||
return new String[] {KeycloakHttpServerAuthenticationMechanism.NAME};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpServerAuthenticationMechanism createAuthenticationMechanism(String mechanismName, Map<String, ?> properties, CallbackHandler callbackHandler) throws HttpAuthenticationException {
|
|
||||||
Map<String, Object> mechanismProperties = new HashMap();
|
|
||||||
|
|
||||||
mechanismProperties.putAll(properties);
|
|
||||||
|
|
||||||
if (KeycloakHttpServerAuthenticationMechanism.NAME.equals(mechanismName)) {
|
|
||||||
return new KeycloakHttpServerAuthenticationMechanism(properties, callbackHandler, this.deploymentContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-104
@@ -1,104 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.keycloak.adapters.elytron;
|
|
||||||
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.security.spec.AlgorithmParameterSpec;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.keycloak.KeycloakPrincipal;
|
|
||||||
import org.keycloak.adapters.AdapterUtils;
|
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
|
||||||
import org.wildfly.security.auth.SupportLevel;
|
|
||||||
import org.wildfly.security.auth.server.RealmIdentity;
|
|
||||||
import org.wildfly.security.auth.server.RealmUnavailableException;
|
|
||||||
import org.wildfly.security.auth.server.SecurityRealm;
|
|
||||||
import org.wildfly.security.authz.Attributes;
|
|
||||||
import org.wildfly.security.authz.AuthorizationIdentity;
|
|
||||||
import org.wildfly.security.authz.MapAttributes;
|
|
||||||
import org.wildfly.security.authz.RoleDecoder;
|
|
||||||
import org.wildfly.security.credential.Credential;
|
|
||||||
import org.wildfly.security.evidence.Evidence;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
|
||||||
*/
|
|
||||||
public class KeycloakSecurityRealm implements SecurityRealm {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RealmIdentity getRealmIdentity(Principal principal) throws RealmUnavailableException {
|
|
||||||
if (principal instanceof KeycloakPrincipal) {
|
|
||||||
return createRealmIdentity((KeycloakPrincipal) principal);
|
|
||||||
}
|
|
||||||
return RealmIdentity.NON_EXISTENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
private RealmIdentity createRealmIdentity(KeycloakPrincipal principal) {
|
|
||||||
return new RealmIdentity() {
|
|
||||||
@Override
|
|
||||||
public Principal getRealmIdentityPrincipal() {
|
|
||||||
return principal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType, String algorithmName, AlgorithmParameterSpec parameterSpec) throws RealmUnavailableException {
|
|
||||||
return SupportLevel.UNSUPPORTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <C extends Credential> C getCredential(Class<C> credentialType) throws RealmUnavailableException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SupportLevel getEvidenceVerifySupport(Class<? extends Evidence> evidenceType, String algorithmName) throws RealmUnavailableException {
|
|
||||||
return SupportLevel.SUPPORTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean verifyEvidence(Evidence evidence) throws RealmUnavailableException {
|
|
||||||
return principal != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean exists() throws RealmUnavailableException {
|
|
||||||
return principal != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthorizationIdentity getAuthorizationIdentity() throws RealmUnavailableException {
|
|
||||||
RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) principal.getKeycloakSecurityContext();
|
|
||||||
Attributes attributes = new MapAttributes();
|
|
||||||
Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
|
|
||||||
|
|
||||||
attributes.addAll(RoleDecoder.KEY_ROLES, roles);
|
|
||||||
|
|
||||||
return AuthorizationIdentity.basicIdentity(attributes);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType, String algorithmName, AlgorithmParameterSpec parameterSpec) throws RealmUnavailableException {
|
|
||||||
return SupportLevel.UNSUPPORTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SupportLevel getEvidenceVerifySupport(Class<? extends Evidence> evidenceType, String algorithmName) throws RealmUnavailableException {
|
|
||||||
return SupportLevel.POSSIBLY_SUPPORTED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loaded 100 of 2728 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user