Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a48072e2a |
No files matched your search
@@ -14,7 +14,6 @@
|
|||||||
nbactions.xml
|
nbactions.xml
|
||||||
nb-configuration.xml
|
nb-configuration.xml
|
||||||
catalog.xml
|
catalog.xml
|
||||||
nbproject
|
|
||||||
|
|
||||||
# Compiled source #
|
# Compiled source #
|
||||||
###################
|
###################
|
||||||
@@ -46,6 +45,3 @@ nbproject
|
|||||||
#########
|
#########
|
||||||
target
|
target
|
||||||
|
|
||||||
# Maven shade
|
|
||||||
#############
|
|
||||||
*dependency-reduced-pom.xml
|
|
||||||
+2
-16
@@ -1,23 +1,9 @@
|
|||||||
language: java
|
language: java
|
||||||
|
|
||||||
cache:
|
|
||||||
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 -Xmx2048m"
|
- MAVEN_OPTS="-Xms512m -Xmx2048m"
|
||||||
matrix:
|
|
||||||
- TESTS=group1
|
|
||||||
- TESTS=group2
|
|
||||||
- TESTS=group3
|
|
||||||
- TESTS=group4
|
|
||||||
- TESTS=adapter
|
|
||||||
- TESTS=old
|
|
||||||
|
|
||||||
jdk:
|
jdk:
|
||||||
- oraclejdk8
|
- oraclejdk8
|
||||||
@@ -26,9 +12,9 @@ before_script:
|
|||||||
- export MAVEN_SKIP_RC=true
|
- export MAVEN_SKIP_RC=true
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- travis_wait 60 mvn install -Pdistribution -DskipTests=true -B -V -q
|
- mvn install -Pdistribution -DskipTests=true -B -V -q
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- ./travis-run-tests.sh $TESTS
|
- mvn test -B
|
||||||
|
|
||||||
sudo: false
|
sudo: false
|
||||||
@@ -9,7 +9,7 @@ For more information about Keycloak visit [Keycloak homepage](http://keycloak.or
|
|||||||
Building
|
Building
|
||||||
--------
|
--------
|
||||||
|
|
||||||
Ensure you have JDK 8 (or newer), Maven 3.1.1 (or newer) and Git installed
|
Ensure you have JDK 8 (or newer), Maven 3.2.1 (or newer) and Git installed
|
||||||
|
|
||||||
java -version
|
java -version
|
||||||
mvn -version
|
mvn -version
|
||||||
@@ -36,7 +36,7 @@ Once completed you will find distribution archives in `distribution`.
|
|||||||
Starting Keycloak
|
Starting Keycloak
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
To start Keycloak during development first build as specified above, then run:
|
To start Keycloak during development first build as specficied above, then run:
|
||||||
|
|
||||||
mvn -f testsuite/integration/pom.xml exec:java -Pkeycloak-server
|
mvn -f testsuite/integration/pom.xml exec:java -Pkeycloak-server
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ To stop the server press `Ctrl + C`.
|
|||||||
|
|
||||||
Help and Documentation
|
Help and Documentation
|
||||||
----------------------
|
----------------------
|
||||||
* [Documentation](http://www.keycloak.org/documentation.html) - User Guide, Admin REST API and Javadocs
|
* [Documentation](http://keycloak.jboss.org/docs) - User Guide, Admin REST API and Javadocs
|
||||||
* [User Mailing List](https://lists.jboss.org/mailman/listinfo/keycloak-user) - Mailing list to ask for help and general questions about Keycloak
|
* [User Mailing List](https://lists.jboss.org/mailman/listinfo/keycloak-user) - Mailing list to ask for help and general questions about Keycloak
|
||||||
* [JIRA](https://issues.jboss.org/projects/KEYCLOAK) - Issue tracker for bugs and feature requests
|
* [JIRA](https://issues.jboss.org/projects/KEYCLOAK) - Issue tracker for bugs and feature requests
|
||||||
|
|
||||||
@@ -65,11 +65,10 @@ Contributing
|
|||||||
* [Testsuite](misc/Testsuite.md) - Details about testsuite, but also how to quickly run Keycloak during development and a few test tools (OTP generation, LDAP server, Mail server)
|
* [Testsuite](misc/Testsuite.md) - Details about testsuite, but also how to quickly run Keycloak during development and a few test tools (OTP generation, LDAP server, Mail server)
|
||||||
* [Database Testing](misc/DatabaseTesting.md) - How to do testing of Keycloak on different databases
|
* [Database Testing](misc/DatabaseTesting.md) - How to do testing of Keycloak on different databases
|
||||||
* [Updating Database](misc/UpdatingDatabaseSchema.md) - How to change the Keycloak database
|
* [Updating Database](misc/UpdatingDatabaseSchema.md) - How to change the Keycloak database
|
||||||
* [Changing the Default keycloak-subsystem Configuration](misc/UpdatingServerConfig.md) - How to update the default keycloak-subsystem config
|
|
||||||
* [Developer Mailing List](https://lists.jboss.org/mailman/listinfo/keycloak-dev) - Mailing list to discuss development of Keycloak
|
* [Developer Mailing List](https://lists.jboss.org/mailman/listinfo/keycloak-dev) - Mailing list to discuss development of Keycloak
|
||||||
|
|
||||||
|
|
||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
|
|
||||||
* [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0)
|
* [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0)
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.5.9.Final</version>
|
<version>1.9.0.CR1</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@@ -65,11 +65,6 @@
|
|||||||
<artifactId>keycloak-core</artifactId>
|
<artifactId>keycloak-core</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-authz-client</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
<artifactId>jackson-core</artifactId>
|
<artifactId>jackson-core</artifactId>
|
||||||
|
|||||||
+60
-62
@@ -17,21 +17,26 @@
|
|||||||
|
|
||||||
package org.keycloak.adapters;
|
package org.keycloak.adapters;
|
||||||
|
|
||||||
|
import org.apache.http.HttpEntity;
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
import org.apache.http.client.HttpClient;
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
|
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
|
||||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
|
||||||
import org.keycloak.adapters.rotation.PublicKeyLocator;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
import org.keycloak.adapters.spi.HttpFacade;
|
||||||
import org.keycloak.common.enums.RelativeUrlsUsed;
|
import org.keycloak.common.enums.RelativeUrlsUsed;
|
||||||
import org.keycloak.common.enums.SslRequired;
|
import org.keycloak.common.enums.SslRequired;
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
|
||||||
import org.keycloak.enums.TokenStore;
|
import org.keycloak.enums.TokenStore;
|
||||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||||
|
import org.keycloak.representations.idm.PublishedRealmRepresentation;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.security.PublicKey;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,7 +57,7 @@ public class AdapterDeploymentContext {
|
|||||||
* during the application deployment's life cycle.
|
* during the application deployment's life cycle.
|
||||||
*
|
*
|
||||||
* @param deployment A KeycloakConfigResolver, possibly missing the Auth
|
* @param deployment A KeycloakConfigResolver, possibly missing the Auth
|
||||||
* Server URL
|
* Server URL and/or Realm Public Key
|
||||||
*/
|
*/
|
||||||
public AdapterDeploymentContext(KeycloakDeployment deployment) {
|
public AdapterDeploymentContext(KeycloakDeployment deployment) {
|
||||||
this.deployment = deployment;
|
this.deployment = deployment;
|
||||||
@@ -74,6 +79,7 @@ public class AdapterDeploymentContext {
|
|||||||
/**
|
/**
|
||||||
* For single-tenant deployments, it complements KeycloakDeployment
|
* For single-tenant deployments, it complements KeycloakDeployment
|
||||||
* by resolving a relative Auth Server's URL based on the current request
|
* by resolving a relative Auth Server's URL based on the current request
|
||||||
|
* and, if needed, will lazily resolve the Realm's Public Key.
|
||||||
*
|
*
|
||||||
* For multi-tenant deployments, defers the resolution of KeycloakDeployment
|
* For multi-tenant deployments, defers the resolution of KeycloakDeployment
|
||||||
* to the KeycloakConfigResolver .
|
* to the KeycloakConfigResolver .
|
||||||
@@ -92,8 +98,8 @@ public class AdapterDeploymentContext {
|
|||||||
if (deployment.getAuthServerBaseUrl() == null) return deployment;
|
if (deployment.getAuthServerBaseUrl() == null) return deployment;
|
||||||
|
|
||||||
KeycloakDeployment resolvedDeployment = resolveUrls(deployment, facade);
|
KeycloakDeployment resolvedDeployment = resolveUrls(deployment, facade);
|
||||||
if (resolvedDeployment.getPublicKeyLocator() == null) {
|
if (resolvedDeployment.getRealmKey() == null) {
|
||||||
throw new RuntimeException("KeycloakDeployment was never initialized through appropriate SPIs");
|
resolveRealmKey(resolvedDeployment);
|
||||||
}
|
}
|
||||||
return resolvedDeployment;
|
return resolvedDeployment;
|
||||||
}
|
}
|
||||||
@@ -109,6 +115,45 @@ public class AdapterDeploymentContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void resolveRealmKey(KeycloakDeployment deployment) {
|
||||||
|
if (deployment.getClient() == null) {
|
||||||
|
throw new RuntimeException("KeycloakDeployment was never initialized through appropriate SPIs");
|
||||||
|
}
|
||||||
|
HttpGet get = new HttpGet(deployment.getRealmInfoUrl());
|
||||||
|
try {
|
||||||
|
HttpResponse response = deployment.getClient().execute(get);
|
||||||
|
int status = response.getStatusLine().getStatusCode();
|
||||||
|
if (status != 200) {
|
||||||
|
close(response);
|
||||||
|
throw new RuntimeException("Unable to resolve realm public key remotely, status = " + status);
|
||||||
|
}
|
||||||
|
HttpEntity entity = response.getEntity();
|
||||||
|
if (entity == null) {
|
||||||
|
throw new RuntimeException("Unable to resolve realm public key remotely. There was no entity.");
|
||||||
|
}
|
||||||
|
InputStream is = entity.getContent();
|
||||||
|
try {
|
||||||
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||||
|
int c;
|
||||||
|
while ((c = is.read()) != -1) {
|
||||||
|
os.write(c);
|
||||||
|
}
|
||||||
|
byte[] bytes = os.toByteArray();
|
||||||
|
String json = new String(bytes);
|
||||||
|
PublishedRealmRepresentation rep = JsonSerialization.readValue(json, PublishedRealmRepresentation.class);
|
||||||
|
deployment.setRealmKey(rep.getPublicKey());
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
is.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Unable to resolve realm public key remotely", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This delegate is used to store temporary, per-request metadata like request resolved URLs.
|
* This delegate is used to store temporary, per-request metadata like request resolved URLs.
|
||||||
* Ever method is delegated except URL get methods and isConfigured()
|
* Ever method is delegated except URL get methods and isConfigured()
|
||||||
@@ -124,7 +169,11 @@ public class AdapterDeploymentContext {
|
|||||||
public void setAuthServerBaseUrl(String authServerBaseUrl) {
|
public void setAuthServerBaseUrl(String authServerBaseUrl) {
|
||||||
this.authServerBaseUrl = authServerBaseUrl;
|
this.authServerBaseUrl = authServerBaseUrl;
|
||||||
KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(authServerBaseUrl);
|
KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(authServerBaseUrl);
|
||||||
resolveUrls(serverBuilder);
|
resolveBrowserUrls(serverBuilder);
|
||||||
|
|
||||||
|
if (delegate.getRelativeUrls() == RelativeUrlsUsed.ALL_REQUESTS) {
|
||||||
|
resolveNonBrowserUrls(serverBuilder);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -162,11 +211,6 @@ public class AdapterDeploymentContext {
|
|||||||
return (this.unregisterNodeUrl != null) ? this.unregisterNodeUrl : delegate.getUnregisterNodeUrl();
|
return (this.unregisterNodeUrl != null) ? this.unregisterNodeUrl : delegate.getUnregisterNodeUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getJwksUrl() {
|
|
||||||
return (this.jwksUrl != null) ? this.jwksUrl : delegate.getJwksUrl();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getResourceName() {
|
public String getResourceName() {
|
||||||
return delegate.getResourceName();
|
return delegate.getResourceName();
|
||||||
@@ -183,13 +227,13 @@ public class AdapterDeploymentContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setPublicKeyLocator(PublicKeyLocator publicKeyLocator) {
|
public PublicKey getRealmKey() {
|
||||||
delegate.setPublicKeyLocator(publicKeyLocator);
|
return delegate.getRealmKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PublicKeyLocator getPublicKeyLocator() {
|
public void setRealmKey(PublicKey realmKey) {
|
||||||
return delegate.getPublicKeyLocator();
|
delegate.setRealmKey(realmKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -342,12 +386,6 @@ public class AdapterDeploymentContext {
|
|||||||
return delegate.getNotBefore();
|
return delegate.getNotBefore();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateNotBefore(int notBefore) {
|
|
||||||
delegate.setNotBefore(notBefore);
|
|
||||||
getPublicKeyLocator().reset(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setExposeToken(boolean exposeToken) {
|
public void setExposeToken(boolean exposeToken) {
|
||||||
delegate.setExposeToken(exposeToken);
|
delegate.setExposeToken(exposeToken);
|
||||||
@@ -422,46 +460,6 @@ public class AdapterDeploymentContext {
|
|||||||
public void setTurnOffChangeSessionIdOnLogin(boolean turnOffChangeSessionIdOnLogin) {
|
public void setTurnOffChangeSessionIdOnLogin(boolean turnOffChangeSessionIdOnLogin) {
|
||||||
delegate.setTurnOffChangeSessionIdOnLogin(turnOffChangeSessionIdOnLogin);
|
delegate.setTurnOffChangeSessionIdOnLogin(turnOffChangeSessionIdOnLogin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getTokenMinimumTimeToLive() {
|
|
||||||
return delegate.getTokenMinimumTimeToLive();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setTokenMinimumTimeToLive(final int tokenMinimumTimeToLive) {
|
|
||||||
delegate.setTokenMinimumTimeToLive(tokenMinimumTimeToLive);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PolicyEnforcer getPolicyEnforcer() {
|
|
||||||
return delegate.getPolicyEnforcer();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) {
|
|
||||||
delegate.setPolicyEnforcer(policyEnforcer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) {
|
|
||||||
delegate.setMinTimeBetweenJwksRequests(minTimeBetweenJwksRequests);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMinTimeBetweenJwksRequests() {
|
|
||||||
return delegate.getMinTimeBetweenJwksRequests();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPublicKeyCacheTtl() {
|
|
||||||
return delegate.getPublicKeyCacheTtl();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPublicKeyCacheTtl(int publicKeyCacheTtl) {
|
|
||||||
delegate.setPublicKeyCacheTtl(publicKeyCacheTtl);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) {
|
protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) {
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ package org.keycloak.adapters;
|
|||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.KeycloakPrincipal;
|
import org.keycloak.KeycloakPrincipal;
|
||||||
|
import org.keycloak.KeycloakSecurityContext;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
|
import org.keycloak.common.util.UriUtils;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -36,6 +38,37 @@ public class AdapterUtils {
|
|||||||
return UUID.randomUUID().toString();
|
return UUID.randomUUID().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Best effort to find origin for REST request calls from web UI application to REST application. In case of relative or absolute
|
||||||
|
* "auth-server-url" is returned the URL from request. In case of "auth-server-url-for-backend-request" used in configuration, it returns
|
||||||
|
* the origin of auth server.
|
||||||
|
*
|
||||||
|
* This may be the optimization in cluster, so if you have keycloak and applications on same host, the REST request doesn't need to
|
||||||
|
* go through loadbalancer, but can be sent directly to same host.
|
||||||
|
*
|
||||||
|
* @param browserRequestURL
|
||||||
|
* @param session
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String getOriginForRestCalls(String browserRequestURL, KeycloakSecurityContext session) {
|
||||||
|
if (session instanceof RefreshableKeycloakSecurityContext) {
|
||||||
|
KeycloakDeployment deployment = ((RefreshableKeycloakSecurityContext)session).getDeployment();
|
||||||
|
switch (deployment.getRelativeUrls()) {
|
||||||
|
case ALL_REQUESTS:
|
||||||
|
case NEVER:
|
||||||
|
// Resolve baseURI from the request
|
||||||
|
return UriUtils.getOrigin(browserRequestURL);
|
||||||
|
case BROWSER_ONLY:
|
||||||
|
// Resolve baseURI from the codeURL (This is already non-relative and based on our hostname)
|
||||||
|
return UriUtils.getOrigin(deployment.getTokenUrl());
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return UriUtils.getOrigin(browserRequestURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static Set<String> getRolesFromSecurityContext(RefreshableKeycloakSecurityContext session) {
|
public static Set<String> getRolesFromSecurityContext(RefreshableKeycloakSecurityContext session) {
|
||||||
Set<String> roles = null;
|
Set<String> roles = null;
|
||||||
AccessToken accessToken = session.getToken();
|
AccessToken accessToken = session.getToken();
|
||||||
|
|||||||
+1
-30
@@ -18,12 +18,10 @@
|
|||||||
package org.keycloak.adapters;
|
package org.keycloak.adapters;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.AuthorizationContext;
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
import org.keycloak.KeycloakSecurityContext;
|
||||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
|
||||||
import org.keycloak.common.util.UriUtils;
|
|
||||||
import org.keycloak.constants.AdapterConstants;
|
import org.keycloak.constants.AdapterConstants;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
|
import org.keycloak.common.util.UriUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -57,9 +55,6 @@ public class AuthenticatedActionsHandler {
|
|||||||
queryBearerToken();
|
queryBearerToken();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!isAuthorized()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,28 +124,4 @@ public class AuthenticatedActionsHandler {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAuthorized() {
|
|
||||||
PolicyEnforcer policyEnforcer = this.deployment.getPolicyEnforcer();
|
|
||||||
|
|
||||||
if (policyEnforcer == null) {
|
|
||||||
log.debugv("Policy enforcement is disabled.");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
OIDCHttpFacade facade = (OIDCHttpFacade) this.facade;
|
|
||||||
AuthorizationContext authorizationContext = policyEnforcer.enforce(facade);
|
|
||||||
RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) facade.getSecurityContext();
|
|
||||||
|
|
||||||
if (session != null) {
|
|
||||||
session.setAuthorizationContext(authorizationContext);
|
|
||||||
|
|
||||||
return authorizationContext.isGranted();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Failed to enforce policy decisions.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+33
-31
@@ -24,17 +24,16 @@ import org.apache.http.client.HttpClient;
|
|||||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||||
import org.apache.http.client.methods.HttpPost;
|
import org.apache.http.client.methods.HttpPost;
|
||||||
import org.apache.http.message.BasicNameValuePair;
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
import org.apache.http.util.EntityUtils;
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
||||||
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.util.Base64;
|
import org.keycloak.common.util.Base64;
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
|
||||||
import org.keycloak.constants.ServiceUrlConstants;
|
import org.keycloak.constants.ServiceUrlConstants;
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
import org.keycloak.representations.AccessTokenResponse;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -86,39 +85,42 @@ public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticat
|
|||||||
|
|
||||||
private AccessTokenResponse getToken(String username, String password) throws Exception {
|
private AccessTokenResponse getToken(String username, String password) throws Exception {
|
||||||
AccessTokenResponse tokenResponse=null;
|
AccessTokenResponse tokenResponse=null;
|
||||||
HttpClient client = deployment.getClient();
|
HttpClient client = new HttpClientBuilder().disableTrustManager().build();
|
||||||
|
|
||||||
HttpPost post = new HttpPost(
|
try {
|
||||||
KeycloakUriBuilder.fromUri(deployment.getAuthServerBaseUrl())
|
HttpPost post = new HttpPost(
|
||||||
.path(ServiceUrlConstants.TOKEN_PATH).build(deployment.getRealm()));
|
KeycloakUriBuilder.fromUri(deployment.getAuthServerBaseUrl())
|
||||||
java.util.List <NameValuePair> formparams = new java.util.ArrayList <NameValuePair>();
|
.path(ServiceUrlConstants.TOKEN_PATH).build(deployment.getRealm()));
|
||||||
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD));
|
java.util.List <NameValuePair> formparams = new java.util.ArrayList <NameValuePair>();
|
||||||
formparams.add(new BasicNameValuePair("username", username));
|
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD));
|
||||||
formparams.add(new BasicNameValuePair("password", password));
|
formparams.add(new BasicNameValuePair("username", username));
|
||||||
|
formparams.add(new BasicNameValuePair("password", password));
|
||||||
|
|
||||||
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
|
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
|
||||||
|
|
||||||
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
|
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
|
||||||
post.setEntity(form);
|
post.setEntity(form);
|
||||||
|
|
||||||
HttpResponse response = client.execute(post);
|
HttpResponse response = client.execute(post);
|
||||||
int status = response.getStatusLine().getStatusCode();
|
int status = response.getStatusLine().getStatusCode();
|
||||||
HttpEntity entity = response.getEntity();
|
HttpEntity entity = response.getEntity();
|
||||||
if (status != 200) {
|
if (status != 200) {
|
||||||
EntityUtils.consumeQuietly(entity);
|
throw new java.io.IOException("Bad status: " + status);
|
||||||
throw new java.io.IOException("Bad status: " + status);
|
}
|
||||||
}
|
if (entity == null) {
|
||||||
if (entity == null) {
|
throw new java.io.IOException("No Entity");
|
||||||
throw new java.io.IOException("No Entity");
|
}
|
||||||
}
|
java.io.InputStream is = entity.getContent();
|
||||||
java.io.InputStream is = entity.getContent();
|
try {
|
||||||
try {
|
tokenResponse = JsonSerialization.readValue(is, AccessTokenResponse.class);
|
||||||
tokenResponse = JsonSerialization.readValue(is, AccessTokenResponse.class);
|
} finally {
|
||||||
} finally {
|
try {
|
||||||
try {
|
is.close();
|
||||||
is.close();
|
} catch (java.io.IOException ignored) { }
|
||||||
} catch (java.io.IOException ignored) { }
|
}
|
||||||
}
|
} finally {
|
||||||
|
client.getConnectionManager().shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
return (tokenResponse);
|
return (tokenResponse);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -18,7 +18,7 @@
|
|||||||
package org.keycloak.adapters;
|
package org.keycloak.adapters;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
import org.keycloak.RSATokenVerifier;
|
||||||
import org.keycloak.adapters.spi.AuthChallenge;
|
import org.keycloak.adapters.spi.AuthChallenge;
|
||||||
import org.keycloak.adapters.spi.AuthOutcome;
|
import org.keycloak.adapters.spi.AuthOutcome;
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
import org.keycloak.adapters.spi.HttpFacade;
|
||||||
@@ -84,7 +84,7 @@ public class BearerTokenRequestAuthenticator {
|
|||||||
|
|
||||||
protected AuthOutcome authenticateToken(HttpFacade exchange, String tokenString) {
|
protected AuthOutcome authenticateToken(HttpFacade exchange, String tokenString) {
|
||||||
try {
|
try {
|
||||||
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
|
||||||
} catch (VerificationException e) {
|
} catch (VerificationException e) {
|
||||||
log.error("Failed to verify token", e);
|
log.error("Failed to verify token", e);
|
||||||
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "invalid_token", e.getMessage());
|
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "invalid_token", e.getMessage());
|
||||||
|
|||||||
+5
-3
@@ -17,17 +17,19 @@
|
|||||||
|
|
||||||
package org.keycloak.adapters;
|
package org.keycloak.adapters;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.KeycloakPrincipal;
|
import org.keycloak.KeycloakPrincipal;
|
||||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
import org.keycloak.RSATokenVerifier;
|
||||||
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.KeycloakUriBuilder;
|
|
||||||
import org.keycloak.constants.AdapterConstants;
|
import org.keycloak.constants.AdapterConstants;
|
||||||
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;
|
||||||
import org.keycloak.representations.IDToken;
|
import org.keycloak.representations.IDToken;
|
||||||
|
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
@@ -71,7 +73,7 @@ public class CookieTokenStore {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Skip check if token is active now. It's supposed to be done later by the caller
|
// Skip check if token is active now. It's supposed to be done later by the caller
|
||||||
AccessToken accessToken = AdapterRSATokenVerifier.verifyToken(accessTokenString, deployment, false, true);
|
AccessToken accessToken = RSATokenVerifier.verifyToken(accessTokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl(), false, true);
|
||||||
IDToken idToken;
|
IDToken idToken;
|
||||||
if (idTokenString != null && idTokenString.length() > 0) {
|
if (idTokenString != null && idTokenString.length() > 0) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,72 +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.apache.http.HttpEntity;
|
|
||||||
import org.apache.http.HttpResponse;
|
|
||||||
import org.apache.http.client.methods.HttpRequestBase;
|
|
||||||
import org.keycloak.util.JsonSerialization;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class HttpAdapterUtils {
|
|
||||||
|
|
||||||
|
|
||||||
public static <T> T sendJsonHttpRequest(KeycloakDeployment deployment, HttpRequestBase httpRequest, Class<T> clazz) throws HttpClientAdapterException {
|
|
||||||
try {
|
|
||||||
HttpResponse response = deployment.getClient().execute(httpRequest);
|
|
||||||
int status = response.getStatusLine().getStatusCode();
|
|
||||||
if (status != 200) {
|
|
||||||
close(response);
|
|
||||||
throw new HttpClientAdapterException("Unexpected status = " + status);
|
|
||||||
}
|
|
||||||
HttpEntity entity = response.getEntity();
|
|
||||||
if (entity == null) {
|
|
||||||
throw new HttpClientAdapterException("There was no entity.");
|
|
||||||
}
|
|
||||||
InputStream is = entity.getContent();
|
|
||||||
try {
|
|
||||||
return JsonSerialization.readValue(is, clazz);
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
is.close();
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new HttpClientAdapterException("IO error", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static void close(HttpResponse response) {
|
|
||||||
if (response.getEntity() != null) {
|
|
||||||
try {
|
|
||||||
response.getEntity().getContent().close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-32
@@ -1,32 +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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class HttpClientAdapterException extends Exception {
|
|
||||||
|
|
||||||
public HttpClientAdapterException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpClientAdapterException(String message, Throwable t) {
|
|
||||||
super(message, t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+11
-39
@@ -17,11 +17,9 @@
|
|||||||
|
|
||||||
package org.keycloak.adapters;
|
package org.keycloak.adapters;
|
||||||
|
|
||||||
import org.apache.http.HttpHost;
|
|
||||||
import org.apache.http.client.CookieStore;
|
import org.apache.http.client.CookieStore;
|
||||||
import org.apache.http.client.HttpClient;
|
import org.apache.http.client.HttpClient;
|
||||||
import org.apache.http.conn.ClientConnectionManager;
|
import org.apache.http.conn.ClientConnectionManager;
|
||||||
import org.apache.http.conn.params.ConnRoutePNames;
|
|
||||||
import org.apache.http.conn.scheme.PlainSocketFactory;
|
import org.apache.http.conn.scheme.PlainSocketFactory;
|
||||||
import org.apache.http.conn.scheme.Scheme;
|
import org.apache.http.conn.scheme.Scheme;
|
||||||
import org.apache.http.conn.scheme.SchemeRegistry;
|
import org.apache.http.conn.scheme.SchemeRegistry;
|
||||||
@@ -36,9 +34,9 @@ import org.apache.http.impl.conn.SingleClientConnManager;
|
|||||||
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
|
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
|
||||||
import org.apache.http.params.BasicHttpParams;
|
import org.apache.http.params.BasicHttpParams;
|
||||||
import org.apache.http.params.HttpConnectionParams;
|
import org.apache.http.params.HttpConnectionParams;
|
||||||
|
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||||
import org.keycloak.common.util.EnvUtil;
|
import org.keycloak.common.util.EnvUtil;
|
||||||
import org.keycloak.common.util.KeystoreUtil;
|
import org.keycloak.common.util.KeystoreUtil;
|
||||||
import org.keycloak.representations.adapters.config.AdapterHttpClientConfig;
|
|
||||||
|
|
||||||
import javax.net.ssl.HostnameVerifier;
|
import javax.net.ssl.HostnameVerifier;
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
@@ -48,7 +46,6 @@ import javax.net.ssl.SSLSocket;
|
|||||||
import javax.net.ssl.TrustManager;
|
import javax.net.ssl.TrustManager;
|
||||||
import javax.net.ssl.X509TrustManager;
|
import javax.net.ssl.X509TrustManager;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
@@ -115,7 +112,6 @@ public class HttpClientBuilder {
|
|||||||
protected TimeUnit socketTimeoutUnits = TimeUnit.MILLISECONDS;
|
protected TimeUnit socketTimeoutUnits = TimeUnit.MILLISECONDS;
|
||||||
protected long establishConnectionTimeout = -1;
|
protected long establishConnectionTimeout = -1;
|
||||||
protected TimeUnit establishConnectionTimeoutUnits = TimeUnit.MILLISECONDS;
|
protected TimeUnit establishConnectionTimeoutUnits = TimeUnit.MILLISECONDS;
|
||||||
protected HttpHost proxyHost;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -125,7 +121,8 @@ public class HttpClientBuilder {
|
|||||||
* @param unit
|
* @param unit
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public HttpClientBuilder socketTimeout(long timeout, TimeUnit unit) {
|
public HttpClientBuilder socketTimeout(long timeout, TimeUnit unit)
|
||||||
|
{
|
||||||
this.socketTimeout = timeout;
|
this.socketTimeout = timeout;
|
||||||
this.socketTimeoutUnits = unit;
|
this.socketTimeoutUnits = unit;
|
||||||
return this;
|
return this;
|
||||||
@@ -138,7 +135,8 @@ public class HttpClientBuilder {
|
|||||||
* @param unit
|
* @param unit
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public HttpClientBuilder establishConnectionTimeout(long timeout, TimeUnit unit) {
|
public HttpClientBuilder establishConnectionTimeout(long timeout, TimeUnit unit)
|
||||||
|
{
|
||||||
this.establishConnectionTimeout = timeout;
|
this.establishConnectionTimeout = timeout;
|
||||||
this.establishConnectionTimeoutUnits = unit;
|
this.establishConnectionTimeoutUnits = unit;
|
||||||
return this;
|
return this;
|
||||||
@@ -289,20 +287,16 @@ public class HttpClientBuilder {
|
|||||||
cm = new SingleClientConnManager(registry);
|
cm = new SingleClientConnManager(registry);
|
||||||
}
|
}
|
||||||
BasicHttpParams params = new BasicHttpParams();
|
BasicHttpParams params = new BasicHttpParams();
|
||||||
|
if (socketTimeout > -1)
|
||||||
if (proxyHost != null) {
|
{
|
||||||
params.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxyHost);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (socketTimeout > -1) {
|
|
||||||
HttpConnectionParams.setSoTimeout(params, (int) socketTimeoutUnits.toMillis(socketTimeout));
|
HttpConnectionParams.setSoTimeout(params, (int) socketTimeoutUnits.toMillis(socketTimeout));
|
||||||
|
|
||||||
}
|
}
|
||||||
if (establishConnectionTimeout > -1) {
|
if (establishConnectionTimeout > -1)
|
||||||
HttpConnectionParams.setConnectionTimeout(params, (int) establishConnectionTimeoutUnits.toMillis(establishConnectionTimeout));
|
{
|
||||||
|
HttpConnectionParams.setConnectionTimeout(params, (int)establishConnectionTimeoutUnits.toMillis(establishConnectionTimeout));
|
||||||
}
|
}
|
||||||
DefaultHttpClient client = new DefaultHttpClient(cm, params);
|
DefaultHttpClient client = new DefaultHttpClient(cm, params);
|
||||||
|
|
||||||
if (disableCookieCache) {
|
if (disableCookieCache) {
|
||||||
client.setCookieStore(new CookieStore() {
|
client.setCookieStore(new CookieStore() {
|
||||||
@Override
|
@Override
|
||||||
@@ -333,7 +327,7 @@ public class HttpClientBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpClient build(AdapterHttpClientConfig adapterConfig) {
|
public HttpClient build(AdapterConfig adapterConfig) {
|
||||||
disableCookieCache(); // 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();
|
||||||
@@ -370,28 +364,6 @@ public class HttpClientBuilder {
|
|||||||
} else {
|
} else {
|
||||||
trustStore(truststore);
|
trustStore(truststore);
|
||||||
}
|
}
|
||||||
|
|
||||||
configureProxyForAuthServerIfProvided(adapterConfig);
|
|
||||||
|
|
||||||
return build();
|
return build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures a the proxy to use for auth-server requests if provided.
|
|
||||||
* <p>
|
|
||||||
* If the given {@link AdapterHttpClientConfig} contains the attribute {@code proxy-url} we use the
|
|
||||||
* given URL as a proxy server, otherwise the proxy configuration is ignored.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param adapterConfig
|
|
||||||
*/
|
|
||||||
private void configureProxyForAuthServerIfProvided(AdapterHttpClientConfig adapterConfig) {
|
|
||||||
|
|
||||||
if (adapterConfig == null || adapterConfig.getProxyUrl() == null || adapterConfig.getProxyUrl().trim().isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
URI uri = URI.create(adapterConfig.getProxyUrl());
|
|
||||||
this.proxyHost = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+47
-73
@@ -20,16 +20,15 @@ package org.keycloak.adapters;
|
|||||||
import org.apache.http.client.HttpClient;
|
import org.apache.http.client.HttpClient;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
|
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
|
||||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
import org.keycloak.constants.ServiceUrlConstants;
|
||||||
import org.keycloak.adapters.rotation.PublicKeyLocator;
|
|
||||||
import org.keycloak.common.enums.RelativeUrlsUsed;
|
import org.keycloak.common.enums.RelativeUrlsUsed;
|
||||||
import org.keycloak.common.enums.SslRequired;
|
import org.keycloak.common.enums.SslRequired;
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
|
||||||
import org.keycloak.constants.ServiceUrlConstants;
|
|
||||||
import org.keycloak.enums.TokenStore;
|
import org.keycloak.enums.TokenStore;
|
||||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||||
|
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.security.PublicKey;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -43,7 +42,7 @@ public class KeycloakDeployment {
|
|||||||
|
|
||||||
protected RelativeUrlsUsed relativeUrls;
|
protected RelativeUrlsUsed relativeUrls;
|
||||||
protected String realm;
|
protected String realm;
|
||||||
protected PublicKeyLocator publicKeyLocator;
|
protected volatile PublicKey realmKey;
|
||||||
protected String authServerBaseUrl;
|
protected String authServerBaseUrl;
|
||||||
protected String realmInfoUrl;
|
protected String realmInfoUrl;
|
||||||
protected KeycloakUriBuilder authUrl;
|
protected KeycloakUriBuilder authUrl;
|
||||||
@@ -52,12 +51,10 @@ public class KeycloakDeployment {
|
|||||||
protected String accountUrl;
|
protected String accountUrl;
|
||||||
protected String registerNodeUrl;
|
protected String registerNodeUrl;
|
||||||
protected String unregisterNodeUrl;
|
protected String unregisterNodeUrl;
|
||||||
protected String jwksUrl;
|
|
||||||
protected String principalAttribute = "sub";
|
protected String principalAttribute = "sub";
|
||||||
|
|
||||||
protected String resourceName;
|
protected String resourceName;
|
||||||
protected boolean bearerOnly;
|
protected boolean bearerOnly;
|
||||||
protected boolean autodetectBearerOnly;
|
|
||||||
protected boolean enableBasicAuth;
|
protected boolean enableBasicAuth;
|
||||||
protected boolean publicClient;
|
protected boolean publicClient;
|
||||||
protected Map<String, Object> resourceCredentials = new HashMap<>();
|
protected Map<String, Object> resourceCredentials = new HashMap<>();
|
||||||
@@ -80,16 +77,12 @@ public class KeycloakDeployment {
|
|||||||
protected boolean turnOffChangeSessionIdOnLogin;
|
protected boolean turnOffChangeSessionIdOnLogin;
|
||||||
|
|
||||||
protected volatile int notBefore;
|
protected volatile int notBefore;
|
||||||
protected int tokenMinimumTimeToLive;
|
|
||||||
protected int minTimeBetweenJwksRequests;
|
|
||||||
protected int publicKeyCacheTtl;
|
|
||||||
private PolicyEnforcer policyEnforcer;
|
|
||||||
|
|
||||||
public KeycloakDeployment() {
|
public KeycloakDeployment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isConfigured() {
|
public boolean isConfigured() {
|
||||||
return getRealm() != null && getPublicKeyLocator() != null && (isBearerOnly() || getAuthServerBaseUrl() != null);
|
return getRealm() != null && getRealmKey() != null && (isBearerOnly() || getAuthServerBaseUrl() != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getResourceName() {
|
public String getResourceName() {
|
||||||
@@ -104,12 +97,12 @@ public class KeycloakDeployment {
|
|||||||
this.realm = realm;
|
this.realm = realm;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PublicKeyLocator getPublicKeyLocator() {
|
public PublicKey getRealmKey() {
|
||||||
return publicKeyLocator;
|
return realmKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPublicKeyLocator(PublicKeyLocator publicKeyLocator) {
|
public void setRealmKey(PublicKey realmKey) {
|
||||||
this.publicKeyLocator = publicKeyLocator;
|
this.realmKey = realmKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAuthServerBaseUrl() {
|
public String getAuthServerBaseUrl() {
|
||||||
@@ -118,17 +111,39 @@ public class KeycloakDeployment {
|
|||||||
|
|
||||||
public void setAuthServerBaseUrl(AdapterConfig config) {
|
public void setAuthServerBaseUrl(AdapterConfig config) {
|
||||||
this.authServerBaseUrl = config.getAuthServerUrl();
|
this.authServerBaseUrl = config.getAuthServerUrl();
|
||||||
if (authServerBaseUrl == null) return;
|
String authServerURLForBackendReqs = config.getAuthServerUrlForBackendRequests();
|
||||||
|
if (authServerBaseUrl == null && authServerURLForBackendReqs == null) return;
|
||||||
|
|
||||||
URI authServerUri = URI.create(authServerBaseUrl);
|
URI authServerUri = null;
|
||||||
|
if (authServerBaseUrl != null) {
|
||||||
|
authServerUri = URI.create(authServerBaseUrl);
|
||||||
|
}
|
||||||
|
|
||||||
if (authServerUri.getHost() == null) {
|
if (authServerUri == null || authServerUri.getHost() == null) {
|
||||||
relativeUrls = RelativeUrlsUsed.ALWAYS;
|
if (authServerURLForBackendReqs != null) {
|
||||||
|
relativeUrls = RelativeUrlsUsed.BROWSER_ONLY;
|
||||||
|
|
||||||
|
KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(authServerURLForBackendReqs);
|
||||||
|
if (serverBuilder.getHost() == null || serverBuilder.getScheme() == null) {
|
||||||
|
throw new IllegalStateException("Relative URL not supported for auth-server-url-for-backend-requests option. URL used: "
|
||||||
|
+ authServerURLForBackendReqs + ", Client: " + config.getResource());
|
||||||
|
}
|
||||||
|
resolveNonBrowserUrls(serverBuilder);
|
||||||
|
} else {
|
||||||
|
relativeUrls = RelativeUrlsUsed.ALL_REQUESTS;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// We have absolute URI in config
|
// We have absolute URI in config
|
||||||
relativeUrls = RelativeUrlsUsed.NEVER;
|
relativeUrls = RelativeUrlsUsed.NEVER;
|
||||||
KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(authServerBaseUrl);
|
KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(authServerBaseUrl);
|
||||||
resolveUrls(serverBuilder);
|
resolveBrowserUrls(serverBuilder);
|
||||||
|
|
||||||
|
if (authServerURLForBackendReqs == null) {
|
||||||
|
resolveNonBrowserUrls(serverBuilder);
|
||||||
|
} else {
|
||||||
|
serverBuilder = KeycloakUriBuilder.fromUri(authServerURLForBackendReqs);
|
||||||
|
resolveNonBrowserUrls(serverBuilder);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,21 +152,29 @@ public class KeycloakDeployment {
|
|||||||
/**
|
/**
|
||||||
* @param authUrlBuilder absolute URI
|
* @param authUrlBuilder absolute URI
|
||||||
*/
|
*/
|
||||||
protected void resolveUrls(KeycloakUriBuilder authUrlBuilder) {
|
protected void resolveBrowserUrls(KeycloakUriBuilder authUrlBuilder) {
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug("resolveUrls");
|
log.debug("resolveBrowserUrls");
|
||||||
}
|
}
|
||||||
|
|
||||||
String login = authUrlBuilder.clone().path(ServiceUrlConstants.AUTH_PATH).build(getRealm()).toString();
|
String login = authUrlBuilder.clone().path(ServiceUrlConstants.AUTH_PATH).build(getRealm()).toString();
|
||||||
authUrl = KeycloakUriBuilder.fromUri(login);
|
authUrl = KeycloakUriBuilder.fromUri(login);
|
||||||
realmInfoUrl = authUrlBuilder.clone().path(ServiceUrlConstants.REALM_INFO_PATH).build(getRealm()).toString();
|
realmInfoUrl = authUrlBuilder.clone().path(ServiceUrlConstants.REALM_INFO_PATH).build(getRealm()).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param authUrlBuilder absolute URI
|
||||||
|
*/
|
||||||
|
protected void resolveNonBrowserUrls(KeycloakUriBuilder authUrlBuilder) {
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("resolveNonBrowserUrls");
|
||||||
|
}
|
||||||
|
|
||||||
tokenUrl = authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_PATH).build(getRealm()).toString();
|
tokenUrl = authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_PATH).build(getRealm()).toString();
|
||||||
logoutUrl = KeycloakUriBuilder.fromUri(authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH).build(getRealm()).toString());
|
logoutUrl = KeycloakUriBuilder.fromUri(authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH).build(getRealm()).toString());
|
||||||
accountUrl = authUrlBuilder.clone().path(ServiceUrlConstants.ACCOUNT_SERVICE_PATH).build(getRealm()).toString();
|
accountUrl = authUrlBuilder.clone().path(ServiceUrlConstants.ACCOUNT_SERVICE_PATH).build(getRealm()).toString();
|
||||||
registerNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_REGISTER_NODE_PATH).build(getRealm()).toString();
|
registerNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_REGISTER_NODE_PATH).build(getRealm()).toString();
|
||||||
unregisterNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_UNREGISTER_NODE_PATH).build(getRealm()).toString();
|
unregisterNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_UNREGISTER_NODE_PATH).build(getRealm()).toString();
|
||||||
jwksUrl = authUrlBuilder.clone().path(ServiceUrlConstants.JWKS_URL).build(getRealm()).toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public RelativeUrlsUsed getRelativeUrls() {
|
public RelativeUrlsUsed getRelativeUrls() {
|
||||||
@@ -186,10 +209,6 @@ public class KeycloakDeployment {
|
|||||||
return unregisterNodeUrl;
|
return unregisterNodeUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getJwksUrl() {
|
|
||||||
return jwksUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setResourceName(String resourceName) {
|
public void setResourceName(String resourceName) {
|
||||||
this.resourceName = resourceName;
|
this.resourceName = resourceName;
|
||||||
}
|
}
|
||||||
@@ -202,14 +221,6 @@ public class KeycloakDeployment {
|
|||||||
this.bearerOnly = bearerOnly;
|
this.bearerOnly = bearerOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAutodetectBearerOnly() {
|
|
||||||
return autodetectBearerOnly;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAutodetectBearerOnly(boolean autodetectBearerOnly) {
|
|
||||||
this.autodetectBearerOnly = autodetectBearerOnly;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isEnableBasicAuth() {
|
public boolean isEnableBasicAuth() {
|
||||||
return enableBasicAuth;
|
return enableBasicAuth;
|
||||||
}
|
}
|
||||||
@@ -338,11 +349,6 @@ public class KeycloakDeployment {
|
|||||||
this.notBefore = notBefore;
|
this.notBefore = notBefore;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateNotBefore(int notBefore) {
|
|
||||||
this.notBefore = notBefore;
|
|
||||||
getPublicKeyLocator().reset(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAlwaysRefreshToken() {
|
public boolean isAlwaysRefreshToken() {
|
||||||
return alwaysRefreshToken;
|
return alwaysRefreshToken;
|
||||||
}
|
}
|
||||||
@@ -382,36 +388,4 @@ public class KeycloakDeployment {
|
|||||||
public void setTurnOffChangeSessionIdOnLogin(boolean turnOffChangeSessionIdOnLogin) {
|
public void setTurnOffChangeSessionIdOnLogin(boolean turnOffChangeSessionIdOnLogin) {
|
||||||
this.turnOffChangeSessionIdOnLogin = turnOffChangeSessionIdOnLogin;
|
this.turnOffChangeSessionIdOnLogin = turnOffChangeSessionIdOnLogin;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getTokenMinimumTimeToLive() {
|
|
||||||
return tokenMinimumTimeToLive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTokenMinimumTimeToLive(final int tokenMinimumTimeToLive) {
|
|
||||||
this.tokenMinimumTimeToLive = tokenMinimumTimeToLive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMinTimeBetweenJwksRequests() {
|
|
||||||
return minTimeBetweenJwksRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) {
|
|
||||||
this.minTimeBetweenJwksRequests = minTimeBetweenJwksRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPublicKeyCacheTtl() {
|
|
||||||
return publicKeyCacheTtl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPublicKeyCacheTtl(int publicKeyCacheTtl) {
|
|
||||||
this.publicKeyCacheTtl = publicKeyCacheTtl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) {
|
|
||||||
this.policyEnforcer = policyEnforcer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PolicyEnforcer getPolicyEnforcer() {
|
|
||||||
return policyEnforcer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+4
-23
@@ -21,14 +21,10 @@ import com.fasterxml.jackson.annotation.JsonInclude;
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
||||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
|
||||||
import org.keycloak.adapters.rotation.HardcodedPublicKeyLocator;
|
|
||||||
import org.keycloak.adapters.rotation.JWKPublicKeyLocator;
|
|
||||||
import org.keycloak.common.enums.SslRequired;
|
import org.keycloak.common.enums.SslRequired;
|
||||||
import org.keycloak.common.util.PemUtils;
|
|
||||||
import org.keycloak.enums.TokenStore;
|
import org.keycloak.enums.TokenStore;
|
||||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
import org.keycloak.common.util.PemUtils;
|
||||||
import org.keycloak.util.SystemPropertiesJsonParserFactory;
|
import org.keycloak.util.SystemPropertiesJsonParserFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -61,16 +57,11 @@ public class KeycloakDeploymentBuilder {
|
|||||||
PublicKey realmKey;
|
PublicKey realmKey;
|
||||||
try {
|
try {
|
||||||
realmKey = PemUtils.decodePublicKey(realmKeyPem);
|
realmKey = PemUtils.decodePublicKey(realmKeyPem);
|
||||||
HardcodedPublicKeyLocator pkLocator = new HardcodedPublicKeyLocator(realmKey);
|
|
||||||
deployment.setPublicKeyLocator(pkLocator);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
} else {
|
deployment.setRealmKey(realmKey);
|
||||||
JWKPublicKeyLocator pkLocator = new JWKPublicKeyLocator();
|
|
||||||
deployment.setPublicKeyLocator(pkLocator);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (adapterConfig.getSslRequired() != null) {
|
if (adapterConfig.getSslRequired() != null) {
|
||||||
deployment.setSslRequired(SslRequired.valueOf(adapterConfig.getSslRequired().toUpperCase()));
|
deployment.setSslRequired(SslRequired.valueOf(adapterConfig.getSslRequired().toUpperCase()));
|
||||||
} else {
|
} else {
|
||||||
@@ -99,35 +90,25 @@ public class KeycloakDeploymentBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deployment.setBearerOnly(adapterConfig.isBearerOnly());
|
deployment.setBearerOnly(adapterConfig.isBearerOnly());
|
||||||
deployment.setAutodetectBearerOnly(adapterConfig.isAutodetectBearerOnly());
|
|
||||||
deployment.setEnableBasicAuth(adapterConfig.isEnableBasicAuth());
|
deployment.setEnableBasicAuth(adapterConfig.isEnableBasicAuth());
|
||||||
deployment.setAlwaysRefreshToken(adapterConfig.isAlwaysRefreshToken());
|
deployment.setAlwaysRefreshToken(adapterConfig.isAlwaysRefreshToken());
|
||||||
deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup());
|
deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup());
|
||||||
deployment.setRegisterNodePeriod(adapterConfig.getRegisterNodePeriod());
|
deployment.setRegisterNodePeriod(adapterConfig.getRegisterNodePeriod());
|
||||||
deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive());
|
|
||||||
deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests());
|
|
||||||
deployment.setPublicKeyCacheTtl(adapterConfig.getPublicKeyCacheTtl());
|
|
||||||
|
|
||||||
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");
|
||||||
}
|
}
|
||||||
if (realmKeyPem == null || !deployment.isBearerOnly() || deployment.isEnableBasicAuth() || deployment.isRegisterNodeAtStartup() || deployment.getRegisterNodePeriod() != -1) {
|
if (realmKeyPem == null || !deployment.isBearerOnly() || deployment.isRegisterNodeAtStartup() || deployment.getRegisterNodePeriod() != -1) {
|
||||||
deployment.setClient(new HttpClientBuilder().build(adapterConfig));
|
deployment.setClient(new HttpClientBuilder().build(adapterConfig));
|
||||||
}
|
}
|
||||||
if (adapterConfig.getAuthServerUrl() == null && (!deployment.isBearerOnly() || realmKeyPem == null)) {
|
if (adapterConfig.getAuthServerUrl() == null && (!deployment.isBearerOnly() || realmKeyPem == null)) {
|
||||||
throw new RuntimeException("You must specify auth-server-url");
|
throw new RuntimeException("You must specify auth-url");
|
||||||
}
|
}
|
||||||
deployment.setAuthServerBaseUrl(adapterConfig);
|
deployment.setAuthServerBaseUrl(adapterConfig);
|
||||||
if (adapterConfig.getTurnOffChangeSessionIdOnLogin() != null) {
|
if (adapterConfig.getTurnOffChangeSessionIdOnLogin() != null) {
|
||||||
deployment.setTurnOffChangeSessionIdOnLogin(adapterConfig.getTurnOffChangeSessionIdOnLogin());
|
deployment.setTurnOffChangeSessionIdOnLogin(adapterConfig.getTurnOffChangeSessionIdOnLogin());
|
||||||
}
|
}
|
||||||
|
|
||||||
PolicyEnforcerConfig policyEnforcerConfig = adapterConfig.getPolicyEnforcerConfig();
|
|
||||||
|
|
||||||
if (policyEnforcerConfig != null) {
|
|
||||||
deployment.setPolicyEnforcer(new PolicyEnforcer(deployment, adapterConfig));
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug("Use authServerUrl: " + deployment.getAuthServerBaseUrl() + ", tokenUrl: " + deployment.getTokenUrl() + ", relativeUrls: " + deployment.getRelativeUrls());
|
log.debug("Use authServerUrl: " + deployment.getAuthServerBaseUrl() + ", tokenUrl: " + deployment.getTokenUrl() + ", relativeUrls: " + deployment.getRelativeUrls());
|
||||||
return deployment;
|
return deployment;
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -17,10 +17,6 @@
|
|||||||
|
|
||||||
package org.keycloak.adapters;
|
package org.keycloak.adapters;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.keycloak.common.util.HostUtils;
|
|
||||||
import org.keycloak.common.util.Time;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -28,6 +24,10 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.common.util.HostUtils;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
|
|||||||
+13
-25
@@ -19,15 +19,12 @@ package org.keycloak.adapters;
|
|||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
import org.keycloak.RSATokenVerifier;
|
||||||
import org.keycloak.adapters.spi.AdapterSessionStore;
|
import org.keycloak.adapters.spi.AdapterSessionStore;
|
||||||
import org.keycloak.adapters.spi.AuthChallenge;
|
import org.keycloak.adapters.spi.AuthChallenge;
|
||||||
import org.keycloak.adapters.spi.AuthOutcome;
|
import org.keycloak.adapters.spi.AuthOutcome;
|
||||||
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.UriUtils;
|
|
||||||
import org.keycloak.constants.AdapterConstants;
|
import org.keycloak.constants.AdapterConstants;
|
||||||
import org.keycloak.enums.TokenStore;
|
import org.keycloak.enums.TokenStore;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
@@ -35,7 +32,8 @@ import org.keycloak.jose.jws.JWSInputException;
|
|||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
import org.keycloak.representations.AccessTokenResponse;
|
||||||
import org.keycloak.representations.IDToken;
|
import org.keycloak.representations.IDToken;
|
||||||
import org.keycloak.util.TokenUtil;
|
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||||
|
import org.keycloak.common.util.UriUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
@@ -161,16 +159,10 @@ public class OAuthRequestAuthenticator {
|
|||||||
String scope = getQueryParamValue(OAuth2Constants.SCOPE);
|
String scope = getQueryParamValue(OAuth2Constants.SCOPE);
|
||||||
url = UriUtils.stripQueryParam(url, OAuth2Constants.SCOPE);
|
url = UriUtils.stripQueryParam(url, OAuth2Constants.SCOPE);
|
||||||
|
|
||||||
String prompt = getQueryParamValue(OAuth2Constants.PROMPT);
|
|
||||||
url = UriUtils.stripQueryParam(url, OAuth2Constants.PROMPT);
|
|
||||||
|
|
||||||
String maxAge = getQueryParamValue(OAuth2Constants.MAX_AGE);
|
|
||||||
url = UriUtils.stripQueryParam(url, OAuth2Constants.MAX_AGE);
|
|
||||||
|
|
||||||
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, Encode.encodeQueryParamAsIs(url)) // Need to encode uri ourselves as queryParam() will not encode % characters.
|
.queryParam(OAuth2Constants.REDIRECT_URI, url)
|
||||||
.queryParam(OAuth2Constants.STATE, state)
|
.queryParam(OAuth2Constants.STATE, state)
|
||||||
.queryParam("login", "true");
|
.queryParam("login", "true");
|
||||||
if(loginHint != null && loginHint.length() > 0){
|
if(loginHint != null && loginHint.length() > 0){
|
||||||
@@ -179,15 +171,9 @@ public class OAuthRequestAuthenticator {
|
|||||||
if (idpHint != null && idpHint.length() > 0) {
|
if (idpHint != null && idpHint.length() > 0) {
|
||||||
redirectUriBuilder.queryParam(AdapterConstants.KC_IDP_HINT,idpHint);
|
redirectUriBuilder.queryParam(AdapterConstants.KC_IDP_HINT,idpHint);
|
||||||
}
|
}
|
||||||
if (prompt != null && prompt.length() > 0) {
|
if (scope != null && scope.length() > 0) {
|
||||||
redirectUriBuilder.queryParam(OAuth2Constants.PROMPT, prompt);
|
redirectUriBuilder.queryParam(OAuth2Constants.SCOPE, scope);
|
||||||
}
|
}
|
||||||
if (maxAge != null && maxAge.length() > 0) {
|
|
||||||
redirectUriBuilder.queryParam(OAuth2Constants.MAX_AGE, maxAge);
|
|
||||||
}
|
|
||||||
|
|
||||||
scope = TokenUtil.attachOIDCScope(scope);
|
|
||||||
redirectUriBuilder.queryParam(OAuth2Constants.SCOPE, scope);
|
|
||||||
|
|
||||||
return redirectUriBuilder.build().toString();
|
return redirectUriBuilder.build().toString();
|
||||||
}
|
}
|
||||||
@@ -196,13 +182,15 @@ public class OAuthRequestAuthenticator {
|
|||||||
return sslRedirectPort;
|
return sslRedirectPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static final AtomicLong counter = new AtomicLong();
|
||||||
|
|
||||||
protected String getStateCode() {
|
protected String getStateCode() {
|
||||||
return AdapterUtils.generateId();
|
return counter.getAndIncrement() + "/" + AdapterUtils.generateId();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AuthChallenge loginRedirect() {
|
protected AuthChallenge loginRedirect() {
|
||||||
final String state = getStateCode();
|
final String state = getStateCode();
|
||||||
final String redirect = getRedirectUri(state);
|
final String redirect = getRedirectUri(state);
|
||||||
if (redirect == null) {
|
if (redirect == null) {
|
||||||
return challenge(403, OIDCAuthenticationError.Reason.NO_REDIRECT_URI, null);
|
return challenge(403, OIDCAuthenticationError.Reason.NO_REDIRECT_URI, null);
|
||||||
}
|
}
|
||||||
@@ -218,7 +206,7 @@ public class OAuthRequestAuthenticator {
|
|||||||
tokenStore.saveRequest();
|
tokenStore.saveRequest();
|
||||||
log.debug("Sending redirect to login page: " + redirect);
|
log.debug("Sending redirect to login page: " + redirect);
|
||||||
exchange.getResponse().setStatus(302);
|
exchange.getResponse().setStatus(302);
|
||||||
exchange.getResponse().setCookie(deployment.getStateCookieName(), state, /* need to set path? */ null, null, -1, deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr()), true);
|
exchange.getResponse().setCookie(deployment.getStateCookieName(), state, /* need to set path? */ null, null, -1, deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr()), false);
|
||||||
exchange.getResponse().setHeader("Location", redirect);
|
exchange.getResponse().setHeader("Location", redirect);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -341,7 +329,7 @@ public class OAuthRequestAuthenticator {
|
|||||||
refreshToken = tokenResponse.getRefreshToken();
|
refreshToken = tokenResponse.getRefreshToken();
|
||||||
idTokenString = tokenResponse.getIdToken();
|
idTokenString = tokenResponse.getIdToken();
|
||||||
try {
|
try {
|
||||||
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
|
||||||
if (idTokenString != null) {
|
if (idTokenString != null) {
|
||||||
try {
|
try {
|
||||||
JWSInput input = new JWSInput(idTokenString);
|
JWSInput input = new JWSInput(idTokenString);
|
||||||
@@ -356,7 +344,7 @@ public class OAuthRequestAuthenticator {
|
|||||||
return challenge(403, OIDCAuthenticationError.Reason.INVALID_TOKEN, null);
|
return challenge(403, OIDCAuthenticationError.Reason.INVALID_TOKEN, null);
|
||||||
}
|
}
|
||||||
if (tokenResponse.getNotBeforePolicy() > deployment.getNotBefore()) {
|
if (tokenResponse.getNotBeforePolicy() > deployment.getNotBefore()) {
|
||||||
deployment.updateNotBefore(tokenResponse.getNotBeforePolicy());
|
deployment.setNotBefore(tokenResponse.getNotBeforePolicy());
|
||||||
}
|
}
|
||||||
if (token.getIssuedAt() < deployment.getNotBefore()) {
|
if (token.getIssuedAt() < deployment.getNotBefore()) {
|
||||||
log.error("Stale token");
|
log.error("Stale token");
|
||||||
|
|||||||
+1
-2
@@ -35,8 +35,7 @@ public class OIDCAuthenticationError implements AuthenticationError {
|
|||||||
CODE_TO_TOKEN_FAILURE,
|
CODE_TO_TOKEN_FAILURE,
|
||||||
INVALID_TOKEN,
|
INVALID_TOKEN,
|
||||||
STALE_TOKEN,
|
STALE_TOKEN,
|
||||||
NO_AUTHORIZATION_HEADER,
|
NO_AUTHORIZATION_HEADER
|
||||||
NO_QUERY_PARAMETER_ACCESS_TOKEN
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Reason reason;
|
private Reason reason;
|
||||||
|
|||||||
+4
-43
@@ -17,32 +17,20 @@
|
|||||||
|
|
||||||
package org.keycloak.adapters;
|
package org.keycloak.adapters;
|
||||||
|
|
||||||
import java.security.PublicKey;
|
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
|
|
||||||
import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
|
|
||||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
import org.keycloak.adapters.spi.HttpFacade;
|
||||||
import org.keycloak.adapters.spi.UserSessionManagement;
|
import org.keycloak.adapters.spi.UserSessionManagement;
|
||||||
import org.keycloak.common.util.StreamUtil;
|
|
||||||
import org.keycloak.jose.jwk.JSONWebKeySet;
|
|
||||||
import org.keycloak.jose.jwk.JWK;
|
|
||||||
import org.keycloak.jose.jwk.JWKBuilder;
|
|
||||||
import org.keycloak.jose.jws.JWSInputException;
|
import org.keycloak.jose.jws.JWSInputException;
|
||||||
import org.keycloak.representations.VersionRepresentation;
|
import org.keycloak.representations.VersionRepresentation;
|
||||||
import org.keycloak.constants.AdapterConstants;
|
import org.keycloak.constants.AdapterConstants;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
import org.keycloak.jose.jws.JWSInputException;
|
|
||||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||||
import org.keycloak.representations.VersionRepresentation;
|
|
||||||
import org.keycloak.representations.adapters.action.AdminAction;
|
import org.keycloak.representations.adapters.action.AdminAction;
|
||||||
import org.keycloak.representations.adapters.action.LogoutAction;
|
import org.keycloak.representations.adapters.action.LogoutAction;
|
||||||
import org.keycloak.representations.adapters.action.PushNotBeforeAction;
|
import org.keycloak.representations.adapters.action.PushNotBeforeAction;
|
||||||
import org.keycloak.representations.adapters.action.TestAvailabilityAction;
|
import org.keycloak.representations.adapters.action.TestAvailabilityAction;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
import org.keycloak.common.util.StreamUtil;
|
||||||
import java.security.PublicKey;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
@@ -94,10 +82,6 @@ public class PreAuthActionsHandler {
|
|||||||
if (!resolveDeployment()) return true;
|
if (!resolveDeployment()) return true;
|
||||||
handleTestAvailable();
|
handleTestAvailable();
|
||||||
return true;
|
return true;
|
||||||
} else if (requestUri.endsWith(AdapterConstants.K_JWKS)) {
|
|
||||||
if (!resolveDeployment()) return true;
|
|
||||||
handleJwksRequest();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -155,7 +139,7 @@ public class PreAuthActionsHandler {
|
|||||||
} else {
|
} else {
|
||||||
log.debugf("logout of all sessions for application '%s'", action.getResource());
|
log.debugf("logout of all sessions for application '%s'", action.getResource());
|
||||||
if (action.getNotBefore() > deployment.getNotBefore()) {
|
if (action.getNotBefore() > deployment.getNotBefore()) {
|
||||||
deployment.updateNotBefore(action.getNotBefore());
|
deployment.setNotBefore(action.getNotBefore());
|
||||||
}
|
}
|
||||||
userSessionManagement.logoutAll();
|
userSessionManagement.logoutAll();
|
||||||
}
|
}
|
||||||
@@ -177,7 +161,7 @@ public class PreAuthActionsHandler {
|
|||||||
}
|
}
|
||||||
PushNotBeforeAction action = JsonSerialization.readValue(token.getContent(), PushNotBeforeAction.class);
|
PushNotBeforeAction action = JsonSerialization.readValue(token.getContent(), PushNotBeforeAction.class);
|
||||||
if (!validateAction(action)) return;
|
if (!validateAction(action)) return;
|
||||||
deployment.updateNotBefore(action.getNotBefore());
|
deployment.setNotBefore(action.getNotBefore());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@@ -214,8 +198,7 @@ public class PreAuthActionsHandler {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
JWSInput input = new JWSInput(token);
|
JWSInput input = new JWSInput(token);
|
||||||
PublicKey publicKey = AdapterRSATokenVerifier.getPublicKey(input.getHeader().getKeyId(), deployment);
|
if (RSAProvider.verify(input, deployment.getRealmKey())) {
|
||||||
if (RSAProvider.verify(input, publicKey)) {
|
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
} catch (JWSInputException ignore) {
|
} catch (JWSInputException ignore) {
|
||||||
@@ -257,26 +240,4 @@ public class PreAuthActionsHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void handleJwksRequest() {
|
|
||||||
try {
|
|
||||||
JSONWebKeySet jwks = new JSONWebKeySet();
|
|
||||||
ClientCredentialsProvider clientCredentialsProvider = deployment.getClientAuthenticator();
|
|
||||||
|
|
||||||
// For now, just get signature key from JWT provider. We can add more if we support encryption etc.
|
|
||||||
if (clientCredentialsProvider instanceof JWTClientCredentialsProvider) {
|
|
||||||
PublicKey publicKey = ((JWTClientCredentialsProvider) clientCredentialsProvider).getPublicKey();
|
|
||||||
JWK jwk = JWKBuilder.create().rs256(publicKey);
|
|
||||||
jwks.setKeys(new JWK[] { jwk });
|
|
||||||
} else {
|
|
||||||
jwks.setKeys(new JWK[] {});
|
|
||||||
}
|
|
||||||
|
|
||||||
facade.getResponse().setStatus(200);
|
|
||||||
facade.getResponse().setHeader("Content-Type", "application/json");
|
|
||||||
JsonSerialization.writeValueToStream(facade.getResponse().getOutputStream(), jwks);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
-54
@@ -1,54 +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.jboss.logging.Logger;
|
|
||||||
import org.keycloak.adapters.spi.AuthOutcome;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:froehlich.ch@gmail.com">Christian Froehlich</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class QueryParamterTokenRequestAuthenticator extends BearerTokenRequestAuthenticator {
|
|
||||||
public static final String ACCESS_TOKEN = "access_token";
|
|
||||||
protected Logger log = Logger.getLogger(QueryParamterTokenRequestAuthenticator.class);
|
|
||||||
|
|
||||||
public QueryParamterTokenRequestAuthenticator(KeycloakDeployment deployment) {
|
|
||||||
super(deployment);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthOutcome authenticate(HttpFacade exchange) {
|
|
||||||
tokenString = null;
|
|
||||||
tokenString = getAccessTokenFromQueryParamter(exchange);
|
|
||||||
if (tokenString == null || tokenString.trim().isEmpty()) {
|
|
||||||
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_QUERY_PARAMETER_ACCESS_TOKEN, null, null);
|
|
||||||
return AuthOutcome.NOT_ATTEMPTED;
|
|
||||||
}
|
|
||||||
return (authenticateToken(exchange, tokenString));
|
|
||||||
}
|
|
||||||
|
|
||||||
String getAccessTokenFromQueryParamter(HttpFacade exchange) {
|
|
||||||
try {
|
|
||||||
if (exchange != null && exchange.getRequest() != null) {
|
|
||||||
return exchange.getRequest().getQueryParamValue(ACCESS_TOKEN);
|
|
||||||
}
|
|
||||||
} catch (Exception ignore) {
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+5
-22
@@ -18,11 +18,9 @@
|
|||||||
package org.keycloak.adapters;
|
package org.keycloak.adapters;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.AuthorizationContext;
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
import org.keycloak.KeycloakSecurityContext;
|
||||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
import org.keycloak.RSATokenVerifier;
|
||||||
import org.keycloak.common.VerificationException;
|
import org.keycloak.common.VerificationException;
|
||||||
import org.keycloak.common.util.Time;
|
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
import org.keycloak.representations.AccessTokenResponse;
|
||||||
import org.keycloak.representations.IDToken;
|
import org.keycloak.representations.IDToken;
|
||||||
@@ -76,11 +74,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isActive() {
|
public boolean isActive() {
|
||||||
return token != null && this.token.isActive() && deployment!=null && this.token.getIssuedAt() > deployment.getNotBefore();
|
return token != null && this.token.isActive() && this.token.getIssuedAt() > deployment.getNotBefore();
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isTokenTimeToLiveSufficient(AccessToken token) {
|
|
||||||
return token != null && (token.getExpiration() - this.deployment.getTokenMinimumTimeToLive()) > Time.currentTime();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeycloakDeployment getDeployment() {
|
public KeycloakDeployment getDeployment() {
|
||||||
@@ -101,7 +95,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
|
|||||||
if (log.isTraceEnabled()) {
|
if (log.isTraceEnabled()) {
|
||||||
log.trace("checking whether to refresh.");
|
log.trace("checking whether to refresh.");
|
||||||
}
|
}
|
||||||
if (isActive() && isTokenTimeToLiveSufficient(this.token)) return true;
|
if (isActive()) return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.deployment == null || refreshToken == null) return false; // Might be serialized in HttpSession?
|
if (this.deployment == null || refreshToken == null) return false; // Might be serialized in HttpSession?
|
||||||
@@ -130,21 +124,14 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
|
|||||||
String tokenString = response.getToken();
|
String tokenString = response.getToken();
|
||||||
AccessToken token = null;
|
AccessToken token = null;
|
||||||
try {
|
try {
|
||||||
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
|
||||||
log.debug("Token Verification succeeded!");
|
log.debug("Token Verification succeeded!");
|
||||||
} catch (VerificationException e) {
|
} catch (VerificationException e) {
|
||||||
log.error("failed verification of token");
|
log.error("failed verification of token");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the TTL is greater-or-equal to the expire time on the refreshed token, have to abort or go into an infinite refresh loop
|
|
||||||
if (!isTokenTimeToLiveSufficient(token)) {
|
|
||||||
log.error("failed to refresh the token with a longer time-to-live than the minimum");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.getNotBeforePolicy() > deployment.getNotBefore()) {
|
if (response.getNotBeforePolicy() > deployment.getNotBefore()) {
|
||||||
deployment.updateNotBefore(response.getNotBeforePolicy());
|
deployment.setNotBefore(response.getNotBeforePolicy());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.token = token;
|
this.token = token;
|
||||||
@@ -158,8 +145,4 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
|
|||||||
tokenStore.refreshCallback(this);
|
tokenStore.refreshCallback(this);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAuthorizationContext(AuthorizationContext authorizationContext) {
|
|
||||||
this.authorizationContext = authorizationContext;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+2
-65
@@ -17,8 +17,6 @@
|
|||||||
|
|
||||||
package org.keycloak.adapters;
|
package org.keycloak.adapters;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.KeycloakPrincipal;
|
import org.keycloak.KeycloakPrincipal;
|
||||||
import org.keycloak.adapters.spi.AuthChallenge;
|
import org.keycloak.adapters.spi.AuthChallenge;
|
||||||
@@ -63,7 +61,7 @@ public abstract class RequestAuthenticator {
|
|||||||
if (log.isTraceEnabled()) {
|
if (log.isTraceEnabled()) {
|
||||||
log.trace("try bearer");
|
log.trace("try bearer");
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthOutcome outcome = bearer.authenticate(facade);
|
AuthOutcome outcome = bearer.authenticate(facade);
|
||||||
if (outcome == AuthOutcome.FAILED) {
|
if (outcome == AuthOutcome.FAILED) {
|
||||||
challenge = bearer.getChallenge();
|
challenge = bearer.getChallenge();
|
||||||
@@ -76,36 +74,18 @@ public abstract class RequestAuthenticator {
|
|||||||
return AuthOutcome.AUTHENTICATED;
|
return AuthOutcome.AUTHENTICATED;
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryParamterTokenRequestAuthenticator queryParamAuth = createQueryParamterTokenRequestAuthenticator();
|
|
||||||
if (log.isTraceEnabled()) {
|
|
||||||
log.trace("try query paramter auth");
|
|
||||||
}
|
|
||||||
|
|
||||||
outcome = queryParamAuth.authenticate(facade);
|
|
||||||
if (outcome == AuthOutcome.FAILED) {
|
|
||||||
challenge = queryParamAuth.getChallenge();
|
|
||||||
log.debug("QueryParamAuth auth FAILED");
|
|
||||||
return AuthOutcome.FAILED;
|
|
||||||
} else if (outcome == AuthOutcome.AUTHENTICATED) {
|
|
||||||
if (verifySSL()) return AuthOutcome.FAILED;
|
|
||||||
log.debug("QueryParamAuth AUTHENTICATED");
|
|
||||||
completeAuthentication(queryParamAuth, "KEYCLOAK");
|
|
||||||
return AuthOutcome.AUTHENTICATED;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deployment.isEnableBasicAuth()) {
|
if (deployment.isEnableBasicAuth()) {
|
||||||
BasicAuthRequestAuthenticator basicAuth = createBasicAuthAuthenticator();
|
BasicAuthRequestAuthenticator basicAuth = createBasicAuthAuthenticator();
|
||||||
if (log.isTraceEnabled()) {
|
if (log.isTraceEnabled()) {
|
||||||
log.trace("try basic auth");
|
log.trace("try basic auth");
|
||||||
}
|
}
|
||||||
|
|
||||||
outcome = basicAuth.authenticate(facade);
|
outcome = basicAuth.authenticate(facade);
|
||||||
if (outcome == AuthOutcome.FAILED) {
|
if (outcome == AuthOutcome.FAILED) {
|
||||||
challenge = basicAuth.getChallenge();
|
challenge = basicAuth.getChallenge();
|
||||||
log.debug("BasicAuth FAILED");
|
log.debug("BasicAuth FAILED");
|
||||||
return AuthOutcome.FAILED;
|
return AuthOutcome.FAILED;
|
||||||
} else if (outcome == AuthOutcome.AUTHENTICATED) {
|
} else if (outcome == AuthOutcome.AUTHENTICATED) {
|
||||||
if (verifySSL()) return AuthOutcome.FAILED;
|
|
||||||
log.debug("BasicAuth AUTHENTICATED");
|
log.debug("BasicAuth AUTHENTICATED");
|
||||||
completeAuthentication(basicAuth, "BASIC");
|
completeAuthentication(basicAuth, "BASIC");
|
||||||
return AuthOutcome.AUTHENTICATED;
|
return AuthOutcome.AUTHENTICATED;
|
||||||
@@ -118,12 +98,6 @@ public abstract class RequestAuthenticator {
|
|||||||
return AuthOutcome.NOT_ATTEMPTED;
|
return AuthOutcome.NOT_ATTEMPTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAutodetectedBearerOnly(facade.getRequest())) {
|
|
||||||
challenge = bearer.getChallenge();
|
|
||||||
log.debug("NOT_ATTEMPTED: Treating as bearer only");
|
|
||||||
return AuthOutcome.NOT_ATTEMPTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (log.isTraceEnabled()) {
|
if (log.isTraceEnabled()) {
|
||||||
log.trace("try oauth");
|
log.trace("try oauth");
|
||||||
}
|
}
|
||||||
@@ -166,36 +140,6 @@ public abstract class RequestAuthenticator {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isAutodetectedBearerOnly(HttpFacade.Request request) {
|
|
||||||
if (!deployment.isAutodetectBearerOnly()) return false;
|
|
||||||
|
|
||||||
String headerValue = facade.getRequest().getHeader("X-Requested-With");
|
|
||||||
if (headerValue != null && headerValue.equalsIgnoreCase("XMLHttpRequest")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
headerValue = facade.getRequest().getHeader("Faces-Request");
|
|
||||||
if (headerValue != null && headerValue.startsWith("partial/")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
headerValue = facade.getRequest().getHeader("SOAPAction");
|
|
||||||
if (headerValue != null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> accepts = facade.getRequest().getHeaders("Accept");
|
|
||||||
if (accepts == null) accepts = Collections.emptyList();
|
|
||||||
|
|
||||||
for (String accept : accepts) {
|
|
||||||
if (accept.contains("text/html") || accept.contains("text/*") || accept.contains("*/*")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract OAuthRequestAuthenticator createOAuthAuthenticator();
|
protected abstract OAuthRequestAuthenticator createOAuthAuthenticator();
|
||||||
|
|
||||||
protected BearerTokenRequestAuthenticator createBearerTokenAuthenticator() {
|
protected BearerTokenRequestAuthenticator createBearerTokenAuthenticator() {
|
||||||
@@ -206,24 +150,17 @@ public abstract class RequestAuthenticator {
|
|||||||
return new BasicAuthRequestAuthenticator(deployment);
|
return new BasicAuthRequestAuthenticator(deployment);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected QueryParamterTokenRequestAuthenticator createQueryParamterTokenRequestAuthenticator() {
|
|
||||||
return new QueryParamterTokenRequestAuthenticator(deployment);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void completeAuthentication(OAuthRequestAuthenticator oauth) {
|
protected void completeAuthentication(OAuthRequestAuthenticator oauth) {
|
||||||
RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, tokenStore, oauth.getTokenString(), oauth.getToken(), oauth.getIdTokenString(), oauth.getIdToken(), oauth.getRefreshToken());
|
RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, tokenStore, oauth.getTokenString(), oauth.getToken(), oauth.getIdTokenString(), oauth.getIdToken(), oauth.getRefreshToken());
|
||||||
final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(AdapterUtils.getPrincipalName(deployment, oauth.getToken()), session);
|
final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(AdapterUtils.getPrincipalName(deployment, oauth.getToken()), session);
|
||||||
completeOAuthAuthentication(principal);
|
completeOAuthAuthentication(principal);
|
||||||
log.debugv("User ''{0}'' invoking ''{1}'' on client ''{2}''", principal.getName(), facade.getRequest().getURI(), deployment.getResourceName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal);
|
protected abstract void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal);
|
||||||
|
|
||||||
protected abstract void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method);
|
protected abstract void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* After code is received, we change the session id if possible to guard against https://www.owasp.org/index.php/Session_Fixation
|
* After code is received, we change the session id if possible to guard against https://www.owasp.org/index.php/Session_Fixation
|
||||||
*
|
|
||||||
* @param create
|
* @param create
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -26,12 +26,12 @@ import org.apache.http.client.methods.HttpPost;
|
|||||||
import org.apache.http.message.BasicNameValuePair;
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
||||||
import org.keycloak.common.util.HostUtils;
|
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
|
||||||
import org.keycloak.common.util.StreamUtil;
|
|
||||||
import org.keycloak.constants.AdapterConstants;
|
import org.keycloak.constants.AdapterConstants;
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
import org.keycloak.representations.AccessTokenResponse;
|
||||||
|
import org.keycloak.common.util.HostUtils;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||||
|
import org.keycloak.common.util.StreamUtil;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|||||||
+2
-2
@@ -17,10 +17,10 @@
|
|||||||
|
|
||||||
package org.keycloak.adapters.authentication;
|
package org.keycloak.adapters.authentication;
|
||||||
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The simple SPI for authenticating clients/applications . It's used by adapter during all OIDC backchannel requests to Keycloak server
|
* The simple SPI for authenticating clients/applications . It's used by adapter during all OIDC backchannel requests to Keycloak server
|
||||||
* (codeToToken exchange, refresh token or backchannel logout) . You can also use it in your application during direct access grants or service account request
|
* (codeToToken exchange, refresh token or backchannel logout) . You can also use it in your application during direct access grants or service account request
|
||||||
|
|||||||
+6
-6
@@ -17,12 +17,6 @@
|
|||||||
|
|
||||||
package org.keycloak.adapters.authentication;
|
package org.keycloak.adapters.authentication;
|
||||||
|
|
||||||
import org.apache.http.NameValuePair;
|
|
||||||
import org.apache.http.client.methods.HttpPost;
|
|
||||||
import org.apache.http.message.BasicNameValuePair;
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -30,6 +24,12 @@ import java.util.Map;
|
|||||||
import java.util.ServiceConfigurationError;
|
import java.util.ServiceConfigurationError;
|
||||||
import java.util.ServiceLoader;
|
import java.util.ServiceLoader;
|
||||||
|
|
||||||
|
import org.apache.http.NameValuePair;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
|
|||||||
+2
-2
@@ -17,14 +17,14 @@
|
|||||||
|
|
||||||
package org.keycloak.adapters.authentication;
|
package org.keycloak.adapters.authentication;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.util.BasicAuthHelper;
|
import org.keycloak.util.BasicAuthHelper;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Traditional OAuth2 authentication of clients based on client_id and client_secret
|
* Traditional OAuth2 authentication of clients based on client_id and client_secret
|
||||||
*
|
*
|
||||||
|
|||||||
+6
-28
@@ -17,24 +17,16 @@
|
|||||||
|
|
||||||
package org.keycloak.adapters.authentication;
|
package org.keycloak.adapters.authentication;
|
||||||
|
|
||||||
import java.security.KeyPair;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.adapters.AdapterUtils;
|
import org.keycloak.adapters.AdapterUtils;
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
import org.keycloak.jose.jwk.JWK;
|
|
||||||
import org.keycloak.jose.jwk.JWKBuilder;
|
|
||||||
import org.keycloak.jose.jws.JWSBuilder;
|
import org.keycloak.jose.jws.JWSBuilder;
|
||||||
import org.keycloak.representations.JsonWebToken;
|
import org.keycloak.representations.JsonWebToken;
|
||||||
import org.keycloak.common.util.KeystoreUtil;
|
import org.keycloak.common.util.KeystoreUtil;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.jose.jws.JWSBuilder;
|
|
||||||
import org.keycloak.representations.JsonWebToken;
|
|
||||||
|
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Client authentication based on JWT signed by client private key .
|
* Client authentication based on JWT signed by client private key .
|
||||||
@@ -46,9 +38,7 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
|
|||||||
|
|
||||||
public static final String PROVIDER_ID = "jwt";
|
public static final String PROVIDER_ID = "jwt";
|
||||||
|
|
||||||
private KeyPair keyPair;
|
private PrivateKey privateKey;
|
||||||
private JWK publicKeyJwk;
|
|
||||||
|
|
||||||
private int tokenTimeout;
|
private int tokenTimeout;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -56,23 +46,14 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
|
|||||||
return PROVIDER_ID;
|
return PROVIDER_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setupKeyPair(KeyPair keyPair) {
|
public void setPrivateKey(PrivateKey privateKey) {
|
||||||
this.keyPair = keyPair;
|
this.privateKey = privateKey;
|
||||||
this.publicKeyJwk = JWKBuilder.create().rs256(keyPair.getPublic());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTokenTimeout(int tokenTimeout) {
|
public void setTokenTimeout(int tokenTimeout) {
|
||||||
this.tokenTimeout = tokenTimeout;
|
this.tokenTimeout = tokenTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected int getTokenTimeout() {
|
|
||||||
return tokenTimeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PublicKey getPublicKey() {
|
|
||||||
return keyPair.getPublic();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(KeycloakDeployment deployment, Object config) {
|
public void init(KeycloakDeployment deployment, Object config) {
|
||||||
if (config == null || !(config instanceof Map)) {
|
if (config == null || !(config instanceof Map)) {
|
||||||
@@ -103,9 +84,7 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
|
|||||||
if (clientKeyAlias == null) {
|
if (clientKeyAlias == null) {
|
||||||
clientKeyAlias = deployment.getResourceName();
|
clientKeyAlias = deployment.getResourceName();
|
||||||
}
|
}
|
||||||
|
this.privateKey = KeystoreUtil.loadPrivateKeyFromKeystore(clientKeystoreFile, clientKeystorePassword, clientKeyPassword, clientKeyAlias, clientKeystoreFormat);
|
||||||
KeyPair keyPair = KeystoreUtil.loadKeyPairFromKeystore(clientKeystoreFile, clientKeystorePassword, clientKeyPassword, clientKeyAlias, clientKeystoreFormat);
|
|
||||||
setupKeyPair(keyPair);
|
|
||||||
|
|
||||||
this.tokenTimeout = asInt(cfg, "token-timeout", 10);
|
this.tokenTimeout = asInt(cfg, "token-timeout", 10);
|
||||||
}
|
}
|
||||||
@@ -137,9 +116,8 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
|
|||||||
public String createSignedRequestToken(String clientId, String realmInfoUrl) {
|
public String createSignedRequestToken(String clientId, String realmInfoUrl) {
|
||||||
JsonWebToken jwt = createRequestToken(clientId, realmInfoUrl);
|
JsonWebToken jwt = createRequestToken(clientId, realmInfoUrl);
|
||||||
return new JWSBuilder()
|
return new JWSBuilder()
|
||||||
.kid(publicKeyJwk.getKeyId())
|
|
||||||
.jsonContent(jwt)
|
.jsonContent(jwt)
|
||||||
.rsa256(keyPair.getPrivate());
|
.rsa256(privateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected JsonWebToken createRequestToken(String clientId, String realmInfoUrl) {
|
protected JsonWebToken createRequestToken(String clientId, String realmInfoUrl) {
|
||||||
|
|||||||
-294
@@ -1,294 +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.net.URI;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.keycloak.AuthorizationContext;
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.OIDCHttpFacade;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade.Request;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade.Response;
|
|
||||||
import org.keycloak.authorization.client.AuthzClient;
|
|
||||||
import org.keycloak.authorization.client.representation.ResourceRepresentation;
|
|
||||||
import org.keycloak.authorization.client.resource.ProtectedResource;
|
|
||||||
import org.keycloak.representations.AccessToken;
|
|
||||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
|
||||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode;
|
|
||||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
|
||||||
import org.keycloak.representations.idm.authorization.Permission;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
|
||||||
*/
|
|
||||||
public abstract class AbstractPolicyEnforcer {
|
|
||||||
|
|
||||||
private static Logger LOGGER = Logger.getLogger(AbstractPolicyEnforcer.class);
|
|
||||||
private final PolicyEnforcerConfig enforcerConfig;
|
|
||||||
private final PolicyEnforcer policyEnforcer;
|
|
||||||
|
|
||||||
private Map<String, PathConfig> paths;
|
|
||||||
private AuthzClient authzClient;
|
|
||||||
private PathMatcher pathMatcher;
|
|
||||||
|
|
||||||
public AbstractPolicyEnforcer(PolicyEnforcer policyEnforcer) {
|
|
||||||
this.policyEnforcer = policyEnforcer;
|
|
||||||
this.enforcerConfig = policyEnforcer.getEnforcerConfig();
|
|
||||||
this.authzClient = policyEnforcer.getClient();
|
|
||||||
this.pathMatcher = new PathMatcher();
|
|
||||||
this.paths = policyEnforcer.getPaths();
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthorizationContext authorize(OIDCHttpFacade httpFacade) {
|
|
||||||
EnforcementMode enforcementMode = this.enforcerConfig.getEnforcementMode();
|
|
||||||
|
|
||||||
if (EnforcementMode.DISABLED.equals(enforcementMode)) {
|
|
||||||
return createEmptyAuthorizationContext(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
KeycloakSecurityContext securityContext = httpFacade.getSecurityContext();
|
|
||||||
|
|
||||||
if (securityContext != null) {
|
|
||||||
AccessToken accessToken = securityContext.getToken();
|
|
||||||
|
|
||||||
if (accessToken != null) {
|
|
||||||
Request request = httpFacade.getRequest();
|
|
||||||
Response response = httpFacade.getResponse();
|
|
||||||
String path = getPath(request);
|
|
||||||
PathConfig pathConfig = this.pathMatcher.matches(path, this.paths);
|
|
||||||
|
|
||||||
LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig);
|
|
||||||
|
|
||||||
if (pathConfig == null) {
|
|
||||||
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
|
|
||||||
return createAuthorizationContext(accessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGER.debugf("Could not find a configuration for path [%s]", path);
|
|
||||||
response.sendError(403, "Could not find a configuration for path [" + path + "].");
|
|
||||||
|
|
||||||
return createEmptyAuthorizationContext(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
|
|
||||||
return createEmptyAuthorizationContext(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
PathConfig actualPathConfig = resolvePathConfig(pathConfig, request);
|
|
||||||
Set<String> requiredScopes = getRequiredScopes(actualPathConfig, request);
|
|
||||||
|
|
||||||
if (isAuthorized(actualPathConfig, requiredScopes, accessToken, httpFacade)) {
|
|
||||||
try {
|
|
||||||
return createAuthorizationContext(accessToken);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Error processing path [" + actualPathConfig.getPath() + "].", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!challenge(actualPathConfig, requiredScopes, httpFacade)) {
|
|
||||||
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
|
|
||||||
response.sendError(403, "Authorization failed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return createEmptyAuthorizationContext(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade);
|
|
||||||
|
|
||||||
protected boolean isAuthorized(PathConfig actualPathConfig, Set<String> requiredScopes, AccessToken accessToken, OIDCHttpFacade httpFacade) {
|
|
||||||
Request request = httpFacade.getRequest();
|
|
||||||
PolicyEnforcerConfig enforcerConfig = getEnforcerConfig();
|
|
||||||
|
|
||||||
if (isDefaultAccessDeniedUri(request, enforcerConfig)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
AccessToken.Authorization authorization = accessToken.getAuthorization();
|
|
||||||
|
|
||||||
if (authorization == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Permission> permissions = authorization.getPermissions();
|
|
||||||
boolean hasPermission = false;
|
|
||||||
|
|
||||||
for (Permission permission : permissions) {
|
|
||||||
if (permission.getResourceSetId() != null) {
|
|
||||||
if (isResourcePermission(actualPathConfig, permission)) {
|
|
||||||
hasPermission = true;
|
|
||||||
|
|
||||||
if (actualPathConfig.isInstance() && !matchResourcePermission(actualPathConfig, permission)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) {
|
|
||||||
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions);
|
|
||||||
if (request.getMethod().equalsIgnoreCase("DELETE") && actualPathConfig.isInstance()) {
|
|
||||||
this.paths.remove(actualPathConfig);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) {
|
|
||||||
hasPermission = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasPermission && EnforcementMode.PERMISSIVE.equals(actualPathConfig.getEnforcementMode())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGER.debugf("Authorization FAILED for path [%s]. No enough permissions [%s].", actualPathConfig, permissions);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isDefaultAccessDeniedUri(Request request, PolicyEnforcerConfig enforcerConfig) {
|
|
||||||
String accessDeniedPath = enforcerConfig.getOnDenyRedirectTo();
|
|
||||||
|
|
||||||
if (accessDeniedPath != null) {
|
|
||||||
if (request.getURI().contains(accessDeniedPath)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasResourceScopePermission(Set<String> requiredScopes, Permission permission, PathConfig actualPathConfig) {
|
|
||||||
Set<String> allowedScopes = permission.getScopes();
|
|
||||||
return (allowedScopes.containsAll(requiredScopes) || allowedScopes.isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected AuthzClient getAuthzClient() {
|
|
||||||
return this.authzClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected PolicyEnforcerConfig getEnforcerConfig() {
|
|
||||||
return enforcerConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected PolicyEnforcer getPolicyEnforcer() {
|
|
||||||
return policyEnforcer;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AuthorizationContext createEmptyAuthorizationContext(final boolean granted) {
|
|
||||||
return new AuthorizationContext() {
|
|
||||||
@Override
|
|
||||||
public boolean hasPermission(String resourceName, String scopeName) {
|
|
||||||
return granted;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasResourcePermission(String resourceName) {
|
|
||||||
return granted;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasScopePermission(String scopeName) {
|
|
||||||
return granted;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Permission> getPermissions() {
|
|
||||||
return Collections.EMPTY_LIST;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isGranted() {
|
|
||||||
return granted;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private PathConfig resolvePathConfig(PathConfig originalConfig, Request request) {
|
|
||||||
String path = getPath(request);
|
|
||||||
|
|
||||||
if (originalConfig.hasPattern()) {
|
|
||||||
ProtectedResource resource = this.authzClient.protection().resource();
|
|
||||||
Set<String> search = resource.findByFilter("uri=" + path);
|
|
||||||
|
|
||||||
if (!search.isEmpty()) {
|
|
||||||
// resource does exist on the server, cache it
|
|
||||||
ResourceRepresentation targetResource = resource.findById(search.iterator().next()).getResourceDescription();
|
|
||||||
PathConfig config = PolicyEnforcer.createPathConfig(targetResource);
|
|
||||||
|
|
||||||
config.setScopes(originalConfig.getScopes());
|
|
||||||
config.setMethods(originalConfig.getMethods());
|
|
||||||
config.setParentConfig(originalConfig);
|
|
||||||
config.setEnforcementMode(originalConfig.getEnforcementMode());
|
|
||||||
|
|
||||||
this.policyEnforcer.addPath(config);
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return originalConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getPath(Request request) {
|
|
||||||
return request.getRelativePath();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<String> getRequiredScopes(PathConfig pathConfig, Request request) {
|
|
||||||
Set<String> requiredScopes = new HashSet<>();
|
|
||||||
|
|
||||||
requiredScopes.addAll(pathConfig.getScopes());
|
|
||||||
|
|
||||||
String method = request.getMethod();
|
|
||||||
|
|
||||||
for (PolicyEnforcerConfig.MethodConfig methodConfig : pathConfig.getMethods()) {
|
|
||||||
if (methodConfig.getMethod().equals(method)) {
|
|
||||||
requiredScopes.addAll(methodConfig.getScopes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return requiredScopes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AuthorizationContext createAuthorizationContext(AccessToken accessToken) {
|
|
||||||
return new AuthorizationContext(accessToken, this.paths);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
|
||||||
// first we try a match using resource id
|
|
||||||
boolean resourceMatch = matchResourcePermission(actualPathConfig, permission);
|
|
||||||
|
|
||||||
// as a fallback, check if the current path is an instance and if so, check if parent's id matches the permission
|
|
||||||
if (!resourceMatch && actualPathConfig.isInstance()) {
|
|
||||||
resourceMatch = matchResourcePermission(actualPathConfig.getParentConfig(), permission);
|
|
||||||
}
|
|
||||||
|
|
||||||
return resourceMatch;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean matchResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
|
||||||
return permission.getResourceSetId().equals(actualPathConfig.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-79
@@ -1,79 +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 org.jboss.logging.Logger;
|
|
||||||
import org.keycloak.adapters.OIDCHttpFacade;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
import org.keycloak.authorization.client.AuthzClient;
|
|
||||||
import org.keycloak.authorization.client.representation.PermissionRequest;
|
|
||||||
import org.keycloak.authorization.client.resource.PermissionResource;
|
|
||||||
import org.keycloak.authorization.client.resource.ProtectionResource;
|
|
||||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
|
||||||
*/
|
|
||||||
public class BearerTokenPolicyEnforcer extends AbstractPolicyEnforcer {
|
|
||||||
|
|
||||||
private static Logger LOGGER = Logger.getLogger(BearerTokenPolicyEnforcer.class);
|
|
||||||
|
|
||||||
public BearerTokenPolicyEnforcer(PolicyEnforcer enforcer) {
|
|
||||||
super(enforcer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
|
|
||||||
if (getEnforcerConfig().getUserManagedAccess() != null) {
|
|
||||||
challengeUmaAuthentication(pathConfig, requiredScopes, facade);
|
|
||||||
} else {
|
|
||||||
challengeEntitlementAuthentication(facade);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void challengeEntitlementAuthentication(OIDCHttpFacade facade) {
|
|
||||||
HttpFacade.Response response = facade.getResponse();
|
|
||||||
AuthzClient authzClient = getAuthzClient();
|
|
||||||
String clientId = authzClient.getConfiguration().getClientId();
|
|
||||||
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/entitlement";
|
|
||||||
response.setStatus(401);
|
|
||||||
response.setHeader("WWW-Authenticate", "KC_ETT realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void challengeUmaAuthentication(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
|
|
||||||
HttpFacade.Response response = facade.getResponse();
|
|
||||||
AuthzClient authzClient = getAuthzClient();
|
|
||||||
String ticket = getPermissionTicket(pathConfig, requiredScopes, authzClient);
|
|
||||||
String clientId = authzClient.getConfiguration().getClientId();
|
|
||||||
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/authorize";
|
|
||||||
response.setStatus(401);
|
|
||||||
response.setHeader("WWW-Authenticate", "UMA realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\",ticket=\"" + ticket + "\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getPermissionTicket(PathConfig pathConfig, Set<String> requiredScopes, AuthzClient authzClient) {
|
|
||||||
ProtectionResource protection = authzClient.protection();
|
|
||||||
PermissionResource permission = protection.permission();
|
|
||||||
PermissionRequest permissionRequest = new PermissionRequest();
|
|
||||||
permissionRequest.setResourceSetId(pathConfig.getId());
|
|
||||||
permissionRequest.setScopes(requiredScopes);
|
|
||||||
return permission.forResource(permissionRequest).getTicket();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-147
@@ -1,147 +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.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.OIDCHttpFacade;
|
|
||||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
import org.keycloak.authorization.client.AuthorizationDeniedException;
|
|
||||||
import org.keycloak.authorization.client.AuthzClient;
|
|
||||||
import org.keycloak.authorization.client.representation.AuthorizationRequest;
|
|
||||||
import org.keycloak.authorization.client.representation.AuthorizationResponse;
|
|
||||||
import org.keycloak.authorization.client.representation.EntitlementRequest;
|
|
||||||
import org.keycloak.authorization.client.representation.EntitlementResponse;
|
|
||||||
import org.keycloak.authorization.client.representation.PermissionRequest;
|
|
||||||
import org.keycloak.authorization.client.representation.PermissionResponse;
|
|
||||||
import org.keycloak.representations.AccessToken;
|
|
||||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
|
||||||
import org.keycloak.representations.idm.authorization.Permission;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
|
||||||
*/
|
|
||||||
public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
|
||||||
|
|
||||||
private static Logger LOGGER = Logger.getLogger(KeycloakAdapterPolicyEnforcer.class);
|
|
||||||
|
|
||||||
public KeycloakAdapterPolicyEnforcer(PolicyEnforcer policyEnforcer) {
|
|
||||||
super(policyEnforcer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isAuthorized(PathConfig pathConfig, Set<String> requiredScopes, AccessToken accessToken, OIDCHttpFacade httpFacade) {
|
|
||||||
AccessToken original = accessToken;
|
|
||||||
|
|
||||||
if (super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
accessToken = requestAuthorizationToken(pathConfig, requiredScopes, httpFacade);
|
|
||||||
|
|
||||||
if (accessToken == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
AccessToken.Authorization authorization = original.getAuthorization();
|
|
||||||
|
|
||||||
if (authorization == null) {
|
|
||||||
authorization = new AccessToken.Authorization();
|
|
||||||
authorization.setPermissions(new ArrayList<Permission>());
|
|
||||||
}
|
|
||||||
|
|
||||||
AccessToken.Authorization newAuthorization = accessToken.getAuthorization();
|
|
||||||
|
|
||||||
if (newAuthorization != null) {
|
|
||||||
authorization.getPermissions().addAll(newAuthorization.getPermissions());
|
|
||||||
}
|
|
||||||
|
|
||||||
original.setAuthorization(authorization);
|
|
||||||
|
|
||||||
return super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
|
|
||||||
String accessDeniedPath = getEnforcerConfig().getOnDenyRedirectTo();
|
|
||||||
HttpFacade.Response response = facade.getResponse();
|
|
||||||
|
|
||||||
if (accessDeniedPath != null) {
|
|
||||||
response.setStatus(302);
|
|
||||||
response.setHeader("Location", accessDeniedPath);
|
|
||||||
} else {
|
|
||||||
response.sendError(403);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AccessToken requestAuthorizationToken(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade httpFacade) {
|
|
||||||
try {
|
|
||||||
String accessToken = httpFacade.getSecurityContext().getTokenString();
|
|
||||||
AuthzClient authzClient = getAuthzClient();
|
|
||||||
KeycloakDeployment deployment = getPolicyEnforcer().getDeployment();
|
|
||||||
|
|
||||||
if (getEnforcerConfig().getUserManagedAccess() != null) {
|
|
||||||
LOGGER.debug("Obtaining authorization for authenticated user.");
|
|
||||||
PermissionRequest permissionRequest = new PermissionRequest();
|
|
||||||
|
|
||||||
permissionRequest.setResourceSetId(pathConfig.getId());
|
|
||||||
permissionRequest.setScopes(requiredScopes);
|
|
||||||
|
|
||||||
PermissionResponse permissionResponse = authzClient.protection().permission().forResource(permissionRequest);
|
|
||||||
AuthorizationRequest authzRequest = new AuthorizationRequest(permissionResponse.getTicket());
|
|
||||||
AuthorizationResponse authzResponse = authzClient.authorization(accessToken).authorize(authzRequest);
|
|
||||||
|
|
||||||
if (authzResponse != null) {
|
|
||||||
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
LOGGER.debug("Obtaining entitlements for authenticated user.");
|
|
||||||
AccessToken token = httpFacade.getSecurityContext().getToken();
|
|
||||||
|
|
||||||
if (token.getAuthorization() == null) {
|
|
||||||
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getClientId());
|
|
||||||
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
|
|
||||||
} else {
|
|
||||||
EntitlementRequest request = new EntitlementRequest();
|
|
||||||
PermissionRequest permissionRequest = new PermissionRequest();
|
|
||||||
permissionRequest.setResourceSetId(pathConfig.getId());
|
|
||||||
permissionRequest.setResourceSetName(pathConfig.getName());
|
|
||||||
permissionRequest.setScopes(new HashSet<>(pathConfig.getScopes()));
|
|
||||||
LOGGER.debugf("Sending entitlements request: resource_set_id [%s], resource_set_name [%s], scopes [%s].", permissionRequest.getResourceSetId(), permissionRequest.getResourceSetName(), permissionRequest.getScopes());
|
|
||||||
request.addPermission(permissionRequest);
|
|
||||||
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).get(authzClient.getConfiguration().getClientId(), request);
|
|
||||||
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (AuthorizationDeniedException e) {
|
|
||||||
LOGGER.debug("Authorization denied", e);
|
|
||||||
return null;
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Unexpected error during authorization request.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-109
@@ -1,109 +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 org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
|
||||||
*/
|
|
||||||
class PathMatcher {
|
|
||||||
|
|
||||||
private static final String ANY_RESOURCE_PATTERN = "/*";
|
|
||||||
|
|
||||||
PathConfig matches(final String requestedUri, Map<String, PathConfig> paths) {
|
|
||||||
PathConfig pathConfig = paths.get(requestedUri);
|
|
||||||
|
|
||||||
if (pathConfig != null) {
|
|
||||||
return pathConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
PathConfig actualConfig = null;
|
|
||||||
|
|
||||||
for (PathConfig entry : paths.values()) {
|
|
||||||
String protectedUri = entry.getPath();
|
|
||||||
String selectedUri = null;
|
|
||||||
|
|
||||||
if (protectedUri.equals(ANY_RESOURCE_PATTERN) && actualConfig == null) {
|
|
||||||
selectedUri = protectedUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
int suffixIndex = protectedUri.indexOf(ANY_RESOURCE_PATTERN + ".");
|
|
||||||
|
|
||||||
if (suffixIndex != -1) {
|
|
||||||
String protectedSuffix = protectedUri.substring(suffixIndex + ANY_RESOURCE_PATTERN.length());
|
|
||||||
|
|
||||||
if (requestedUri.endsWith(protectedSuffix)) {
|
|
||||||
selectedUri = protectedUri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (protectedUri.equals(requestedUri)) {
|
|
||||||
selectedUri = protectedUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (protectedUri.endsWith(ANY_RESOURCE_PATTERN)) {
|
|
||||||
String formattedPattern = removeWildCardsFromUri(protectedUri);
|
|
||||||
|
|
||||||
if (!formattedPattern.equals("/") && requestedUri.startsWith(formattedPattern)) {
|
|
||||||
selectedUri = protectedUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!formattedPattern.equals("/") && formattedPattern.endsWith("/") && formattedPattern.substring(0, formattedPattern.length() - 1).equals(requestedUri)) {
|
|
||||||
selectedUri = protectedUri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int startRegex = protectedUri.indexOf('{');
|
|
||||||
|
|
||||||
if (startRegex != -1) {
|
|
||||||
String prefix = protectedUri.substring(0, startRegex);
|
|
||||||
|
|
||||||
if (requestedUri.startsWith(prefix)) {
|
|
||||||
selectedUri = protectedUri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedUri != null) {
|
|
||||||
selectedUri = protectedUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedUri != null) {
|
|
||||||
if (actualConfig == null) {
|
|
||||||
actualConfig = entry;
|
|
||||||
} else {
|
|
||||||
if (actualConfig.equals(ANY_RESOURCE_PATTERN)) {
|
|
||||||
actualConfig = entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (protectedUri.startsWith(removeWildCardsFromUri(actualConfig.getPath()))) {
|
|
||||||
actualConfig = entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return actualConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String removeWildCardsFromUri(String protectedUri) {
|
|
||||||
return protectedUri.replaceAll("/[*]", "/");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-234
@@ -1,234 +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 org.jboss.logging.Logger;
|
|
||||||
import org.keycloak.AuthorizationContext;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.OIDCHttpFacade;
|
|
||||||
import org.keycloak.authorization.client.AuthzClient;
|
|
||||||
import org.keycloak.authorization.client.Configuration;
|
|
||||||
import org.keycloak.authorization.client.representation.RegistrationResponse;
|
|
||||||
import org.keycloak.authorization.client.representation.ResourceRepresentation;
|
|
||||||
import org.keycloak.authorization.client.representation.ScopeRepresentation;
|
|
||||||
import org.keycloak.authorization.client.resource.ProtectedResource;
|
|
||||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
|
||||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
|
||||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
|
||||||
import org.keycloak.representations.idm.authorization.Permission;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
|
||||||
*/
|
|
||||||
public class PolicyEnforcer {
|
|
||||||
|
|
||||||
private static Logger LOGGER = Logger.getLogger(PolicyEnforcer.class);
|
|
||||||
|
|
||||||
private final KeycloakDeployment deployment;
|
|
||||||
private final AuthzClient authzClient;
|
|
||||||
private final PolicyEnforcerConfig enforcerConfig;
|
|
||||||
private final Map<String, PathConfig> paths;
|
|
||||||
|
|
||||||
public PolicyEnforcer(KeycloakDeployment deployment, AdapterConfig adapterConfig) {
|
|
||||||
this.deployment = deployment;
|
|
||||||
this.enforcerConfig = adapterConfig.getPolicyEnforcerConfig();
|
|
||||||
this.authzClient = AuthzClient.create(new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), deployment.getClient()));
|
|
||||||
this.paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig);
|
|
||||||
|
|
||||||
if (LOGGER.isDebugEnabled()) {
|
|
||||||
LOGGER.debug("Initialization complete. Path configurations:");
|
|
||||||
for (PathConfig pathConfig : this.paths.values()) {
|
|
||||||
LOGGER.debug(pathConfig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthorizationContext enforce(OIDCHttpFacade facade) {
|
|
||||||
if (LOGGER.isDebugEnabled()) {
|
|
||||||
LOGGER.debugv("Policy enforcement is enable. Enforcing policy decisions for path [{0}].", facade.getRequest().getURI());
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthorizationContext context;
|
|
||||||
|
|
||||||
if (deployment.isBearerOnly()) {
|
|
||||||
context = new BearerTokenPolicyEnforcer(this).authorize(facade);
|
|
||||||
} else {
|
|
||||||
context = new KeycloakAdapterPolicyEnforcer(this).authorize(facade);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LOGGER.isDebugEnabled()) {
|
|
||||||
LOGGER.debugv("Policy enforcement result for path [{0}] is : {1}", facade.getRequest().getURI(), context.isGranted() ? "GRANTED" : "DENIED");
|
|
||||||
LOGGER.debugv("Returning authorization context with permissions:");
|
|
||||||
for (Permission permission : context.getPermissions()) {
|
|
||||||
LOGGER.debug(permission);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
PolicyEnforcerConfig getEnforcerConfig() {
|
|
||||||
return enforcerConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthzClient getClient() {
|
|
||||||
return authzClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, PathConfig> getPaths() {
|
|
||||||
return paths;
|
|
||||||
}
|
|
||||||
|
|
||||||
void addPath(PathConfig pathConfig) {
|
|
||||||
paths.put(pathConfig.getPath(), pathConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
KeycloakDeployment getDeployment() {
|
|
||||||
return deployment;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, PathConfig> configurePaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
|
|
||||||
boolean loadPathsFromServer = true;
|
|
||||||
|
|
||||||
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
|
|
||||||
if (!PolicyEnforcerConfig.EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
|
|
||||||
loadPathsFromServer = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loadPathsFromServer) {
|
|
||||||
LOGGER.info("No path provided in configuration.");
|
|
||||||
return configureAllPathsForResourceServer(protectedResource);
|
|
||||||
} else {
|
|
||||||
LOGGER.info("Paths provided in configuration.");
|
|
||||||
return configureDefinedPaths(protectedResource, enforcerConfig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, PathConfig> configureDefinedPaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
|
|
||||||
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>());
|
|
||||||
|
|
||||||
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
|
|
||||||
Set<String> search;
|
|
||||||
String resourceName = pathConfig.getName();
|
|
||||||
String path = pathConfig.getPath();
|
|
||||||
|
|
||||||
if (resourceName != null) {
|
|
||||||
LOGGER.debugf("Trying to find resource with name [%s] for path [%s].", resourceName, path);
|
|
||||||
search = protectedResource.findByFilter("name=" + resourceName);
|
|
||||||
} else {
|
|
||||||
LOGGER.debugf("Trying to find resource with uri [%s] for path [%s].", path, path);
|
|
||||||
search = protectedResource.findByFilter("uri=" + path);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (search.isEmpty()) {
|
|
||||||
if (enforcerConfig.isCreateResources()) {
|
|
||||||
LOGGER.debugf("Creating resource on server for path [%s].", pathConfig);
|
|
||||||
ResourceRepresentation resource = new ResourceRepresentation();
|
|
||||||
|
|
||||||
resource.setName(resourceName);
|
|
||||||
resource.setType(pathConfig.getType());
|
|
||||||
resource.setUri(path);
|
|
||||||
|
|
||||||
HashSet<ScopeRepresentation> scopes = new HashSet<>();
|
|
||||||
|
|
||||||
for (String scopeName : pathConfig.getScopes()) {
|
|
||||||
ScopeRepresentation scope = new ScopeRepresentation();
|
|
||||||
|
|
||||||
scope.setName(scopeName);
|
|
||||||
|
|
||||||
scopes.add(scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
resource.setScopes(scopes);
|
|
||||||
|
|
||||||
RegistrationResponse registrationResponse = protectedResource.create(resource);
|
|
||||||
|
|
||||||
pathConfig.setId(registrationResponse.getId());
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("Could not find matching resource on server with uri [" + path + "] or name [" + resourceName + "]. Make sure you have created a resource on the server that matches with the path configuration.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pathConfig.setId(search.iterator().next());
|
|
||||||
}
|
|
||||||
|
|
||||||
PathConfig existingPath = null;
|
|
||||||
|
|
||||||
for (PathConfig current : paths.values()) {
|
|
||||||
if (current.getId().equals(pathConfig.getId()) && current.getPath().equals(pathConfig.getPath())) {
|
|
||||||
existingPath = current;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingPath == null) {
|
|
||||||
paths.put(pathConfig.getPath(), pathConfig);
|
|
||||||
} else {
|
|
||||||
existingPath.getMethods().addAll(pathConfig.getMethods());
|
|
||||||
existingPath.getScopes().addAll(pathConfig.getScopes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return paths;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, PathConfig> configureAllPathsForResourceServer(ProtectedResource protectedResource) {
|
|
||||||
LOGGER.info("Querying the server for all resources associated with this application.");
|
|
||||||
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>());
|
|
||||||
|
|
||||||
for (String id : protectedResource.findAll()) {
|
|
||||||
RegistrationResponse response = protectedResource.findById(id);
|
|
||||||
ResourceRepresentation resourceDescription = response.getResourceDescription();
|
|
||||||
|
|
||||||
if (resourceDescription.getUri() != null) {
|
|
||||||
PathConfig pathConfig = createPathConfig(resourceDescription);
|
|
||||||
paths.put(pathConfig.getPath(), pathConfig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return paths;
|
|
||||||
}
|
|
||||||
|
|
||||||
static PathConfig createPathConfig(ResourceRepresentation resourceDescription) {
|
|
||||||
PathConfig pathConfig = new PathConfig();
|
|
||||||
|
|
||||||
pathConfig.setId(resourceDescription.getId());
|
|
||||||
pathConfig.setName(resourceDescription.getName());
|
|
||||||
pathConfig.setPath(resourceDescription.getUri());
|
|
||||||
|
|
||||||
List<String> scopeNames = new ArrayList<>();
|
|
||||||
|
|
||||||
for (ScopeRepresentation scope : resourceDescription.getScopes()) {
|
|
||||||
scopeNames.add(scope.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
pathConfig.setScopes(scopeNames);
|
|
||||||
pathConfig.setType(resourceDescription.getType());
|
|
||||||
|
|
||||||
return pathConfig;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+25
-20
@@ -17,17 +17,14 @@
|
|||||||
|
|
||||||
package org.keycloak.adapters.jaas;
|
package org.keycloak.adapters.jaas;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import java.io.InputStream;
|
||||||
import org.keycloak.KeycloakPrincipal;
|
import java.lang.reflect.Constructor;
|
||||||
import org.keycloak.adapters.AdapterUtils;
|
import java.security.Principal;
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
import java.util.HashSet;
|
||||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
import java.util.Map;
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
import java.util.Set;
|
||||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import org.keycloak.common.VerificationException;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import org.keycloak.common.util.FindFile;
|
|
||||||
import org.keycloak.common.util.reflections.Reflections;
|
|
||||||
import org.keycloak.representations.AccessToken;
|
|
||||||
|
|
||||||
import javax.security.auth.Subject;
|
import javax.security.auth.Subject;
|
||||||
import javax.security.auth.callback.Callback;
|
import javax.security.auth.callback.Callback;
|
||||||
@@ -37,14 +34,19 @@ import javax.security.auth.callback.PasswordCallback;
|
|||||||
import javax.security.auth.callback.UnsupportedCallbackException;
|
import javax.security.auth.callback.UnsupportedCallbackException;
|
||||||
import javax.security.auth.login.LoginException;
|
import javax.security.auth.login.LoginException;
|
||||||
import javax.security.auth.spi.LoginModule;
|
import javax.security.auth.spi.LoginModule;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.lang.reflect.Constructor;
|
import org.jboss.logging.Logger;
|
||||||
import java.security.Principal;
|
import org.keycloak.KeycloakPrincipal;
|
||||||
import java.util.HashSet;
|
import org.keycloak.RSATokenVerifier;
|
||||||
import java.util.Map;
|
import org.keycloak.common.VerificationException;
|
||||||
import java.util.Set;
|
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import org.keycloak.adapters.AdapterUtils;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import org.keycloak.common.util.FindFile;
|
||||||
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
|
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||||
|
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||||
|
import org.keycloak.representations.AccessToken;
|
||||||
|
import org.keycloak.common.util.reflections.Reflections;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
@@ -86,6 +88,9 @@ public abstract class AbstractKeycloakLoginModule implements LoginModule {
|
|||||||
try {
|
try {
|
||||||
InputStream is = FindFile.findFile(keycloakConfigFile);
|
InputStream is = FindFile.findFile(keycloakConfigFile);
|
||||||
KeycloakDeployment kd = KeycloakDeploymentBuilder.build(is);
|
KeycloakDeployment kd = KeycloakDeploymentBuilder.build(is);
|
||||||
|
if (kd.getRealmKey() == null) {
|
||||||
|
new AdapterDeploymentContext().resolveRealmKey(kd);
|
||||||
|
}
|
||||||
return kd;
|
return kd;
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
getLogger().debug("Unable to find or parse file " + keycloakConfigFile + " due to " + e.getMessage(), e);
|
getLogger().debug("Unable to find or parse file " + keycloakConfigFile + " due to " + e.getMessage(), e);
|
||||||
@@ -185,7 +190,7 @@ public abstract class AbstractKeycloakLoginModule implements LoginModule {
|
|||||||
|
|
||||||
|
|
||||||
protected Auth bearerAuth(String tokenString) throws VerificationException {
|
protected Auth bearerAuth(String tokenString) throws VerificationException {
|
||||||
AccessToken token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
AccessToken token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
|
||||||
|
|
||||||
boolean verifyCaller;
|
boolean verifyCaller;
|
||||||
if (deployment.isUseResourceRoleMappings()) {
|
if (deployment.isUseResourceRoleMappings()) {
|
||||||
|
|||||||
Executable → Regular
+21
-21
@@ -17,6 +17,19 @@
|
|||||||
|
|
||||||
package org.keycloak.adapters.jaas;
|
package org.keycloak.adapters.jaas;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.security.auth.Subject;
|
||||||
|
import javax.security.auth.callback.CallbackHandler;
|
||||||
|
import javax.security.auth.login.LoginException;
|
||||||
|
|
||||||
import org.apache.http.HttpEntity;
|
import org.apache.http.HttpEntity;
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
import org.apache.http.NameValuePair;
|
import org.apache.http.NameValuePair;
|
||||||
@@ -26,25 +39,12 @@ import org.apache.http.client.methods.HttpPost;
|
|||||||
import org.apache.http.message.BasicNameValuePair;
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
|
||||||
import org.keycloak.common.VerificationException;
|
import org.keycloak.common.VerificationException;
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
||||||
import org.keycloak.constants.ServiceUrlConstants;
|
import org.keycloak.constants.ServiceUrlConstants;
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
import org.keycloak.representations.AccessTokenResponse;
|
||||||
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
|
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||||
import javax.security.auth.Subject;
|
|
||||||
import javax.security.auth.callback.CallbackHandler;
|
|
||||||
import javax.security.auth.login.LoginException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login module based on Resource Owner password credentials grant from OAuth2 specs. It's supposed to be used in environments. which
|
* Login module based on Resource Owner password credentials grant from OAuth2 specs. It's supposed to be used in environments. which
|
||||||
@@ -102,9 +102,9 @@ public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule {
|
|||||||
StringBuilder errorBuilder = new StringBuilder("Login failed. Invalid status: " + status);
|
StringBuilder errorBuilder = new StringBuilder("Login failed. Invalid status: " + status);
|
||||||
if (entity != null) {
|
if (entity != null) {
|
||||||
InputStream is = entity.getContent();
|
InputStream is = entity.getContent();
|
||||||
OAuth2ErrorRepresentation errorRep = JsonSerialization.readValue(is, OAuth2ErrorRepresentation.class);
|
Map<String, String> errors = (Map<String, String>) JsonSerialization.readValue(is, Map.class);
|
||||||
errorBuilder.append(", OAuth2 error. Error: " + errorRep.getError())
|
errorBuilder.append(", OAuth2 error. Error: " + errors.get(OAuth2Constants.ERROR))
|
||||||
.append(", Error description: " + errorRep.getErrorDescription());
|
.append(", Error description: " + errors.get(OAuth2Constants.ERROR_DESCRIPTION));
|
||||||
}
|
}
|
||||||
String error = errorBuilder.toString();
|
String error = errorBuilder.toString();
|
||||||
log.warn(error);
|
log.warn(error);
|
||||||
@@ -161,9 +161,9 @@ public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule {
|
|||||||
if (entity != null) {
|
if (entity != null) {
|
||||||
InputStream is = entity.getContent();
|
InputStream is = entity.getContent();
|
||||||
if (status == 400) {
|
if (status == 400) {
|
||||||
OAuth2ErrorRepresentation errorRep = JsonSerialization.readValue(is, OAuth2ErrorRepresentation.class);
|
Map<String, String> errors = (Map<String, String>) JsonSerialization.readValue(is, Map.class);
|
||||||
errorBuilder.append(", OAuth2 error. Error: " + errorRep.getError())
|
errorBuilder.append(", OAuth2 error. Error: " + errors.get(OAuth2Constants.ERROR))
|
||||||
.append(", Error description: " + errorRep.getErrorDescription());
|
.append(", Error description: " + errors.get(OAuth2Constants.ERROR_DESCRIPTION));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (is != null) is.close();
|
if (is != null) is.close();
|
||||||
|
|||||||
Executable → Regular
File mode changed.
-58
@@ -1,58 +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.rotation;
|
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.keycloak.RSATokenVerifier;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.common.VerificationException;
|
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
|
||||||
import org.keycloak.representations.AccessToken;
|
|
||||||
|
|
||||||
import java.security.PublicKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class AdapterRSATokenVerifier {
|
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(AdapterRSATokenVerifier.class);
|
|
||||||
|
|
||||||
public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment) throws VerificationException {
|
|
||||||
return verifyToken(tokenString, deployment, true, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static PublicKey getPublicKey(String kid, KeycloakDeployment deployment) throws VerificationException {
|
|
||||||
PublicKeyLocator pkLocator = deployment.getPublicKeyLocator();
|
|
||||||
|
|
||||||
PublicKey publicKey = pkLocator.getPublicKey(kid, deployment);
|
|
||||||
if (publicKey == null) {
|
|
||||||
log.errorf("Didn't find publicKey for kid: %s", kid);
|
|
||||||
throw new VerificationException("Didn't find publicKey for specified kid");
|
|
||||||
}
|
|
||||||
|
|
||||||
return publicKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment, boolean checkActive, boolean checkTokenType) throws VerificationException {
|
|
||||||
RSATokenVerifier verifier = RSATokenVerifier.create(tokenString).realmUrl(deployment.getRealmInfoUrl()).checkActive(checkActive).checkTokenType(checkTokenType);
|
|
||||||
PublicKey publicKey = getPublicKey(verifier.getHeader().getKeyId(), deployment);
|
|
||||||
return verifier.publicKey(publicKey).verify().getToken();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-45
@@ -1,45 +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.rotation;
|
|
||||||
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
|
||||||
|
|
||||||
import java.security.PublicKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class HardcodedPublicKeyLocator implements PublicKeyLocator {
|
|
||||||
|
|
||||||
private PublicKey publicKey;
|
|
||||||
|
|
||||||
public HardcodedPublicKeyLocator(PublicKey publicKey) {
|
|
||||||
this.publicKey = publicKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
|
|
||||||
return publicKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reset(KeycloakDeployment deployment) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-115
@@ -1,115 +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.rotation;
|
|
||||||
|
|
||||||
import org.apache.http.client.methods.HttpGet;
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.keycloak.adapters.HttpAdapterUtils;
|
|
||||||
import org.keycloak.adapters.HttpClientAdapterException;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.common.util.Time;
|
|
||||||
import org.keycloak.jose.jwk.JSONWebKeySet;
|
|
||||||
import org.keycloak.jose.jwk.JWK;
|
|
||||||
import org.keycloak.util.JWKSUtils;
|
|
||||||
|
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When needed, publicKeys are downloaded by sending request to realm's jwks_url
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class JWKPublicKeyLocator implements PublicKeyLocator {
|
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(JWKPublicKeyLocator.class);
|
|
||||||
|
|
||||||
private Map<String, PublicKey> currentKeys = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
private volatile int lastRequestTime = 0;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
|
|
||||||
int minTimeBetweenRequests = deployment.getMinTimeBetweenJwksRequests();
|
|
||||||
int publicKeyCacheTtl = deployment.getPublicKeyCacheTtl();
|
|
||||||
int currentTime = Time.currentTime();
|
|
||||||
|
|
||||||
// Check if key is in cache.
|
|
||||||
PublicKey publicKey = lookupCachedKey(publicKeyCacheTtl, currentTime, kid);
|
|
||||||
if (publicKey != null) {
|
|
||||||
return publicKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we are allowed to send request
|
|
||||||
synchronized (this) {
|
|
||||||
currentTime = Time.currentTime();
|
|
||||||
if (currentTime > lastRequestTime + minTimeBetweenRequests) {
|
|
||||||
sendRequest(deployment);
|
|
||||||
lastRequestTime = currentTime;
|
|
||||||
} else {
|
|
||||||
log.debug("Won't send request to realm jwks url. Last request time was " + lastRequestTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
return lookupCachedKey(publicKeyCacheTtl, currentTime, kid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reset(KeycloakDeployment deployment) {
|
|
||||||
synchronized (this) {
|
|
||||||
sendRequest(deployment);
|
|
||||||
lastRequestTime = Time.currentTime();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private PublicKey lookupCachedKey(int publicKeyCacheTtl, int currentTime, String kid) {
|
|
||||||
if (lastRequestTime + publicKeyCacheTtl > currentTime) {
|
|
||||||
return currentKeys.get(kid);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void sendRequest(KeycloakDeployment deployment) {
|
|
||||||
if (log.isTraceEnabled()) {
|
|
||||||
log.trace("Going to send request to retrieve new set of realm public keys for client " + deployment.getResourceName());
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpGet getMethod = new HttpGet(deployment.getJwksUrl());
|
|
||||||
try {
|
|
||||||
JSONWebKeySet jwks = HttpAdapterUtils.sendJsonHttpRequest(deployment, getMethod, JSONWebKeySet.class);
|
|
||||||
|
|
||||||
Map<String, PublicKey> publicKeys = JWKSUtils.getKeysForUse(jwks, JWK.Use.SIG);
|
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debug("Realm public keys successfully retrieved for client " + deployment.getResourceName() + ". New kids: " + publicKeys.keySet().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update current keys
|
|
||||||
currentKeys.clear();
|
|
||||||
currentKeys.putAll(publicKeys);
|
|
||||||
|
|
||||||
} catch (HttpClientAdapterException e) {
|
|
||||||
log.error("Error when sending request to retrieve realm keys", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-44
@@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.adapters.rotation;
|
|
||||||
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
|
||||||
|
|
||||||
import java.security.PublicKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public interface PublicKeyLocator {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param kid
|
|
||||||
* @param deployment
|
|
||||||
* @return publicKey, which should be used for verify signature on given "input"
|
|
||||||
*/
|
|
||||||
PublicKey getPublicKey(String kid, KeycloakDeployment deployment);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset the state of locator (eg. clear the cached keys)
|
|
||||||
*
|
|
||||||
* @param deployment
|
|
||||||
*/
|
|
||||||
void reset(KeycloakDeployment deployment);
|
|
||||||
|
|
||||||
}
|
|
||||||
+3
-16
@@ -21,12 +21,10 @@ import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.adapters.authentication.ClientIdAndSecretCredentialsProvider;
|
import org.keycloak.adapters.authentication.ClientIdAndSecretCredentialsProvider;
|
||||||
import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
|
import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
|
||||||
import org.keycloak.adapters.rotation.HardcodedPublicKeyLocator;
|
|
||||||
import org.keycloak.adapters.rotation.JWKPublicKeyLocator;
|
|
||||||
import org.keycloak.common.enums.RelativeUrlsUsed;
|
import org.keycloak.common.enums.RelativeUrlsUsed;
|
||||||
import org.keycloak.common.enums.SslRequired;
|
import org.keycloak.common.enums.SslRequired;
|
||||||
import org.keycloak.common.util.PemUtils;
|
|
||||||
import org.keycloak.enums.TokenStore;
|
import org.keycloak.enums.TokenStore;
|
||||||
|
import org.keycloak.common.util.PemUtils;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
@@ -41,11 +39,7 @@ public class KeycloakDeploymentBuilderTest {
|
|||||||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak.json"));
|
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak.json"));
|
||||||
assertEquals("demo", deployment.getRealm());
|
assertEquals("demo", deployment.getRealm());
|
||||||
assertEquals("customer-portal", deployment.getResourceName());
|
assertEquals("customer-portal", deployment.getResourceName());
|
||||||
|
assertEquals(PemUtils.decodePublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"), deployment.getRealmKey());
|
||||||
assertTrue(deployment.getPublicKeyLocator() instanceof HardcodedPublicKeyLocator);
|
|
||||||
assertEquals(PemUtils.decodePublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"),
|
|
||||||
deployment.getPublicKeyLocator().getPublicKey(null, deployment));
|
|
||||||
|
|
||||||
assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/auth", deployment.getAuthUrl().build().toString());
|
assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/auth", deployment.getAuthUrl().build().toString());
|
||||||
assertEquals(SslRequired.EXTERNAL, deployment.getSslRequired());
|
assertEquals(SslRequired.EXTERNAL, deployment.getSslRequired());
|
||||||
assertTrue(deployment.isUseResourceRoleMappings());
|
assertTrue(deployment.isUseResourceRoleMappings());
|
||||||
@@ -60,26 +54,19 @@ public class KeycloakDeploymentBuilderTest {
|
|||||||
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());
|
||||||
assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/token", deployment.getTokenUrl());
|
assertEquals("https://backend:8443/auth/realms/demo/protocol/openid-connect/token", deployment.getTokenUrl());
|
||||||
assertEquals(RelativeUrlsUsed.NEVER, deployment.getRelativeUrls());
|
assertEquals(RelativeUrlsUsed.NEVER, deployment.getRelativeUrls());
|
||||||
assertTrue(deployment.isAlwaysRefreshToken());
|
assertTrue(deployment.isAlwaysRefreshToken());
|
||||||
assertTrue(deployment.isRegisterNodeAtStartup());
|
assertTrue(deployment.isRegisterNodeAtStartup());
|
||||||
assertEquals(1000, deployment.getRegisterNodePeriod());
|
assertEquals(1000, deployment.getRegisterNodePeriod());
|
||||||
assertEquals(TokenStore.COOKIE, deployment.getTokenStore());
|
assertEquals(TokenStore.COOKIE, deployment.getTokenStore());
|
||||||
assertEquals("email", deployment.getPrincipalAttribute());
|
assertEquals("email", deployment.getPrincipalAttribute());
|
||||||
assertEquals(10, deployment.getTokenMinimumTimeToLive());
|
|
||||||
assertEquals(20, deployment.getMinTimeBetweenJwksRequests());
|
|
||||||
assertEquals(120, deployment.getPublicKeyCacheTtl());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void loadNoClientCredentials() throws Exception {
|
public void loadNoClientCredentials() throws Exception {
|
||||||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-no-credentials.json"));
|
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-no-credentials.json"));
|
||||||
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
|
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
|
||||||
|
|
||||||
assertTrue(deployment.getPublicKeyLocator() instanceof JWKPublicKeyLocator);
|
|
||||||
assertEquals(10, deployment.getMinTimeBetweenJwksRequests());
|
|
||||||
assertEquals(86400, deployment.getPublicKeyCacheTtl());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
package org.keycloak.adapters;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.representations.oidc.TokenMetadataRepresentation;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
/**
|
|
||||||
* @author github.com/tubbynl
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class RefreshableKeycloakSecurityContextTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void isActive() {
|
|
||||||
TokenMetadataRepresentation token = new TokenMetadataRepresentation();
|
|
||||||
token.setActive(true);
|
|
||||||
token.issuedNow();
|
|
||||||
RefreshableKeycloakSecurityContext sut = new RefreshableKeycloakSecurityContext(null,null,null,token,null, null, null);
|
|
||||||
|
|
||||||
// verify false if null deployment (KEYCLOAK-3050; yielded a npe)
|
|
||||||
assertFalse(sut.isActive());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"realm": "demo",
|
"realm": "demo",
|
||||||
"resource": "customer-portal",
|
"resource": "customer-portal",
|
||||||
|
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||||
"auth-server-url": "https://localhost:8443/auth",
|
"auth-server-url": "https://localhost:8443/auth",
|
||||||
"public-client": true,
|
"public-client": true,
|
||||||
"expose-token": true
|
"expose-token": true
|
||||||
|
|||||||
@@ -24,12 +24,10 @@
|
|||||||
"client-keystore": "classpath:/keystore.jks",
|
"client-keystore": "classpath:/keystore.jks",
|
||||||
"client-keystore-password": "storepass",
|
"client-keystore-password": "storepass",
|
||||||
"client-key-password": "keypass",
|
"client-key-password": "keypass",
|
||||||
|
"auth-server-url-for-backend-requests": "https://backend:8443/auth",
|
||||||
"always-refresh-token": true,
|
"always-refresh-token": true,
|
||||||
"register-node-at-startup": true,
|
"register-node-at-startup": true,
|
||||||
"register-node-period": 1000,
|
"register-node-period": 1000,
|
||||||
"token-store": "cookie",
|
"token-store": "cookie",
|
||||||
"principal-attribute": "email",
|
"principal-attribute": "email"
|
||||||
"token-minimum-time-to-live": 10,
|
|
||||||
"min-time-between-jwks-requests": 20,
|
|
||||||
"public-key-cache-ttl": 120
|
|
||||||
}
|
}
|
||||||
@@ -19,10 +19,10 @@
|
|||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<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">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-as7-integration-pom</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.5.9.Final</version>
|
<version>1.9.0.CR1</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,10 @@
|
|||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<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">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-as7-integration-pom</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.5.9.Final</version>
|
<version>1.9.0.CR1</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|||||||
+4
@@ -22,11 +22,15 @@ import org.apache.catalina.connector.Request;
|
|||||||
import org.apache.catalina.connector.Response;
|
import org.apache.catalina.connector.Response;
|
||||||
import org.apache.catalina.core.StandardContext;
|
import org.apache.catalina.core.StandardContext;
|
||||||
import org.apache.catalina.deploy.LoginConfig;
|
import org.apache.catalina.deploy.LoginConfig;
|
||||||
|
import org.apache.catalina.realm.GenericPrincipal;
|
||||||
import org.keycloak.adapters.tomcat.AbstractKeycloakAuthenticatorValve;
|
import org.keycloak.adapters.tomcat.AbstractKeycloakAuthenticatorValve;
|
||||||
import org.keycloak.adapters.tomcat.GenericPrincipalFactory;
|
import org.keycloak.adapters.tomcat.GenericPrincipalFactory;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keycloak authentication valve
|
* Keycloak authentication valve
|
||||||
|
|||||||
@@ -20,9 +20,9 @@
|
|||||||
|
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-as7-integration-pom</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<version>2.5.9.Final</version>
|
<version>1.9.0.CR1</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>keycloak-as7-subsystem</artifactId>
|
<artifactId>keycloak-as7-subsystem</artifactId>
|
||||||
@@ -64,25 +64,25 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.as</groupId>
|
<groupId>org.jboss.as</groupId>
|
||||||
<artifactId>jboss-as-naming</artifactId>
|
<artifactId>jboss-as-naming</artifactId>
|
||||||
<version>${jboss.as.version}</version>
|
<version>${jboss.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.as</groupId>
|
<groupId>org.jboss.as</groupId>
|
||||||
<artifactId>jboss-as-server</artifactId>
|
<artifactId>jboss-as-server</artifactId>
|
||||||
<version>${jboss.as.version}</version>
|
<version>${jboss.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.as</groupId>
|
<groupId>org.jboss.as</groupId>
|
||||||
<artifactId>jboss-as-ee</artifactId>
|
<artifactId>jboss-as-ee</artifactId>
|
||||||
<version>${jboss.as.version}</version>
|
<version>${jboss.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.as</groupId>
|
<groupId>org.jboss.as</groupId>
|
||||||
<artifactId>jboss-as-web</artifactId>
|
<artifactId>jboss-as-web</artifactId>
|
||||||
<version>${jboss.as.version}</version>
|
<version>${jboss.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
+8
-8
@@ -49,6 +49,7 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
|
|||||||
@Override
|
@Override
|
||||||
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
||||||
DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
||||||
|
String deploymentName = deploymentUnit.getName();
|
||||||
|
|
||||||
// if it's not a web-app there's nothing to secure
|
// if it's not a web-app there's nothing to secure
|
||||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||||
@@ -66,25 +67,24 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
|
|||||||
// otherwise
|
// otherwise
|
||||||
LoginConfigMetaData loginConfig = webMetaData.getLoginConfig();
|
LoginConfigMetaData loginConfig = webMetaData.getLoginConfig();
|
||||||
|
|
||||||
boolean hasSubsystemConfig = service.isSecureDeployment(deploymentUnit);
|
boolean hasSubsystemConfig = service.isSecureDeployment(deploymentName);
|
||||||
boolean webRequiresKC = loginConfig != null && "KEYCLOAK".equalsIgnoreCase(loginConfig.getAuthMethod());
|
boolean webRequiresKC = loginConfig != null && "KEYCLOAK".equalsIgnoreCase(loginConfig.getAuthMethod());
|
||||||
boolean isConfigured = service.isDeploymentConfigured(deploymentUnit);
|
|
||||||
|
|
||||||
if ((hasSubsystemConfig && isConfigured) || webRequiresKC) {
|
if (hasSubsystemConfig || webRequiresKC) {
|
||||||
log.debug("Setting up KEYCLOAK auth method for WAR: " + deploymentUnit.getName());
|
log.debug("Setting up KEYCLOAK auth method for WAR: " + deploymentName);
|
||||||
|
|
||||||
// if secure-deployment configuration exists for web app, we force KEYCLOAK auth method on it
|
// if secure-deployment configuration exists for web app, we force KEYCLOAK auth method on it
|
||||||
if (hasSubsystemConfig) {
|
if (hasSubsystemConfig) {
|
||||||
addJSONData(service.getJSON(deploymentUnit), warMetaData);
|
addJSONData(service.getJSON(deploymentName), warMetaData);
|
||||||
if (loginConfig != null) {
|
if (loginConfig != null) {
|
||||||
loginConfig.setAuthMethod("KEYCLOAK");
|
loginConfig.setAuthMethod("KEYCLOAK");
|
||||||
loginConfig.setRealmName(service.getRealmName(deploymentUnit));
|
loginConfig.setRealmName(service.getRealmName(deploymentName));
|
||||||
} else {
|
} else {
|
||||||
log.warn("Failed to set up KEYCLOAK auth method for WAR: " + deploymentUnit.getName() + " (loginConfig == null)");
|
log.warn("Failed to set up KEYCLOAK auth method for WAR: " + deploymentName + " (loginConfig == null)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addValve(webMetaData);
|
addValve(webMetaData);
|
||||||
KeycloakLogger.ROOT_LOGGER.deploymentSecured(deploymentUnit.getName());
|
KeycloakLogger.ROOT_LOGGER.deploymentSecured(deploymentName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-44
@@ -17,12 +17,9 @@
|
|||||||
|
|
||||||
package org.keycloak.subsystem.as7;
|
package org.keycloak.subsystem.as7;
|
||||||
|
|
||||||
import org.jboss.as.server.deployment.DeploymentUnit;
|
|
||||||
import org.jboss.as.web.deployment.WarMetaData;
|
|
||||||
import org.jboss.dmr.ModelNode;
|
import org.jboss.dmr.ModelNode;
|
||||||
import org.jboss.dmr.Property;
|
import org.jboss.dmr.Property;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.metadata.web.jboss.JBossWebMetaData;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -157,23 +154,14 @@ public final class KeycloakAdapterConfigService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRealmName(DeploymentUnit deploymentUnit) {
|
public String getRealmName(String deploymentName) {
|
||||||
ModelNode deployment = getSecureDeployment(deploymentUnit);
|
ModelNode deployment = this.secureDeployments.get(deploymentName);
|
||||||
return deployment.get(RealmDefinition.TAG_NAME).asString();
|
return deployment.get(RealmDefinition.TAG_NAME).asString();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isDeploymentConfigured(DeploymentUnit deploymentUnit) {
|
public String getJSON(String deploymentName) {
|
||||||
ModelNode deployment = getSecureDeployment(deploymentUnit);
|
ModelNode deployment = this.secureDeployments.get(deploymentName);
|
||||||
if (! deployment.isDefined()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ModelNode resource = deployment.get(SecureDeploymentDefinition.RESOURCE.getName());
|
|
||||||
return resource.isDefined();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getJSON(DeploymentUnit deploymentUnit) {
|
|
||||||
ModelNode deployment = getSecureDeployment(deploymentUnit);
|
|
||||||
String realmName = deployment.get(RealmDefinition.TAG_NAME).asString();
|
String realmName = deployment.get(RealmDefinition.TAG_NAME).asString();
|
||||||
ModelNode realm = this.realms.get(realmName);
|
ModelNode realm = this.realms.get(realmName);
|
||||||
|
|
||||||
@@ -196,36 +184,9 @@ public final class KeycloakAdapterConfigService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSecureDeployment(DeploymentUnit deploymentUnit) {
|
public boolean isSecureDeployment(String deploymentName) {
|
||||||
//log.info("********* CHECK KEYCLOAK DEPLOYMENT: deployments.size()" + deployments.size());
|
//log.info("********* CHECK KEYCLOAK DEPLOYMENT: deployments.size()" + deployments.size());
|
||||||
|
|
||||||
String deploymentName = preferredDeploymentName(deploymentUnit);
|
|
||||||
return this.secureDeployments.containsKey(deploymentName);
|
return this.secureDeployments.containsKey(deploymentName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ModelNode getSecureDeployment(DeploymentUnit deploymentUnit) {
|
|
||||||
String deploymentName = preferredDeploymentName(deploymentUnit);
|
|
||||||
return this.secureDeployments.containsKey(deploymentName)
|
|
||||||
? this.secureDeployments.get(deploymentName)
|
|
||||||
: new ModelNode();
|
|
||||||
}
|
|
||||||
|
|
||||||
// KEYCLOAK-3273: prefer module name if available
|
|
||||||
private String preferredDeploymentName(DeploymentUnit deploymentUnit) {
|
|
||||||
String deploymentName = deploymentUnit.getName();
|
|
||||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
|
||||||
if (warMetaData == null) {
|
|
||||||
return deploymentName;
|
|
||||||
}
|
|
||||||
|
|
||||||
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
|
||||||
if (webMetaData == null) {
|
|
||||||
return deploymentName;
|
|
||||||
}
|
|
||||||
|
|
||||||
String moduleName = webMetaData.getModuleName();
|
|
||||||
if (moduleName != null) return moduleName + ".war";
|
|
||||||
|
|
||||||
return deploymentName;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+2
-4
@@ -46,11 +46,9 @@ public abstract class KeycloakDependencyProcessor implements DeploymentUnitProce
|
|||||||
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
||||||
final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
||||||
|
|
||||||
if (!KeycloakAdapterConfigService.getInstance().isSecureDeployment(deploymentUnit)) {
|
String deploymentName = deploymentUnit.getName();
|
||||||
|
if (!KeycloakAdapterConfigService.getInstance().isSecureDeployment(deploymentName)) {
|
||||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||||
if (warMetaData == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
||||||
if (webMetaData == null) {
|
if (webMetaData == null) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
+1
@@ -25,6 +25,7 @@ import org.jboss.as.server.AbstractDeploymentChainStep;
|
|||||||
import org.jboss.as.server.DeploymentProcessorTarget;
|
import org.jboss.as.server.DeploymentProcessorTarget;
|
||||||
import org.jboss.as.server.deployment.Phase;
|
import org.jboss.as.server.deployment.Phase;
|
||||||
import org.jboss.dmr.ModelNode;
|
import org.jboss.dmr.ModelNode;
|
||||||
|
|
||||||
import org.jboss.msc.service.ServiceController;
|
import org.jboss.msc.service.ServiceController;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|||||||
+3
-3
@@ -86,7 +86,7 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
|
|||||||
|
|
||||||
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addRealm)) {
|
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addRealm)) {
|
||||||
//TODO: externalize the message
|
//TODO: externalize the message
|
||||||
throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-manager is false.");
|
throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
|
||||||
}
|
}
|
||||||
|
|
||||||
list.add(addRealm);
|
list.add(addRealm);
|
||||||
@@ -117,7 +117,7 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
|
|||||||
* TODO need to check realm-ref first.
|
* TODO need to check realm-ref first.
|
||||||
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addSecureDeployment)) {
|
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addSecureDeployment)) {
|
||||||
//TODO: externalize the message
|
//TODO: externalize the message
|
||||||
throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-manager is false.");
|
throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
|
|||||||
credentialsToAdd.add(addCredential);
|
credentialsToAdd.add(addCredential);
|
||||||
} else {
|
} else {
|
||||||
for (Map.Entry<String, String> entry : values.entrySet()) {
|
for (Map.Entry<String, String> entry : values.entrySet()) {
|
||||||
ModelNode addCredential = getCredentialToAdd(parent, name + "." + entry.getKey(), entry.getValue());
|
ModelNode addCredential = getCredentialToAdd(parent, name + "" + entry.getKey(), entry.getValue());
|
||||||
credentialsToAdd.add(addCredential);
|
credentialsToAdd.add(addCredential);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -46,7 +46,7 @@ public final class RealmAddHandler extends AbstractAddStepHandler {
|
|||||||
|
|
||||||
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(model.clone())) {
|
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(model.clone())) {
|
||||||
//TODO: externalize message
|
//TODO: externalize message
|
||||||
throw new OperationFailedException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-manager is false.");
|
throw new OperationFailedException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
@@ -23,6 +23,8 @@ import org.jboss.as.controller.OperationContext;
|
|||||||
import org.jboss.as.controller.OperationFailedException;
|
import org.jboss.as.controller.OperationFailedException;
|
||||||
import org.jboss.dmr.ModelNode;
|
import org.jboss.dmr.ModelNode;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update an attribute on a realm.
|
* Update an attribute on a realm.
|
||||||
*
|
*
|
||||||
|
|||||||
-22
@@ -23,7 +23,6 @@ import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
|
|||||||
import org.jboss.as.controller.SimpleResourceDefinition;
|
import org.jboss.as.controller.SimpleResourceDefinition;
|
||||||
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
|
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
|
||||||
import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
|
import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
|
||||||
import org.jboss.as.controller.operations.validation.IntRangeValidator;
|
|
||||||
import org.jboss.as.controller.operations.validation.StringLengthValidator;
|
import org.jboss.as.controller.operations.validation.StringLengthValidator;
|
||||||
import org.jboss.as.controller.registry.ManagementResourceRegistration;
|
import org.jboss.as.controller.registry.ManagementResourceRegistration;
|
||||||
import org.jboss.as.controller.registry.OperationEntry;
|
import org.jboss.as.controller.registry.OperationEntry;
|
||||||
@@ -80,24 +79,6 @@ class SecureDeploymentDefinition extends SimpleResourceDefinition {
|
|||||||
.setAllowExpression(true)
|
.setAllowExpression(true)
|
||||||
.setDefaultValue(new ModelNode(false))
|
.setDefaultValue(new ModelNode(false))
|
||||||
.build();
|
.build();
|
||||||
protected static final SimpleAttributeDefinition TURN_OFF_CHANGE_SESSION =
|
|
||||||
new SimpleAttributeDefinitionBuilder("turn-off-change-session-id-on-login", ModelType.BOOLEAN, true)
|
|
||||||
.setXmlName("turn-off-change-session-id-on-login")
|
|
||||||
.setAllowExpression(true)
|
|
||||||
.setDefaultValue(new ModelNode(false))
|
|
||||||
.build();
|
|
||||||
protected static final SimpleAttributeDefinition TOKEN_MINIMUM_TIME_TO_LIVE =
|
|
||||||
new SimpleAttributeDefinitionBuilder("token-minimum-time-to-live", ModelType.INT, true)
|
|
||||||
.setXmlName("token-minimum-time-to-live")
|
|
||||||
.setValidator(new IntRangeValidator(-1, true))
|
|
||||||
.setAllowExpression(true)
|
|
||||||
.build();
|
|
||||||
protected static final SimpleAttributeDefinition MIN_TIME_BETWEEN_JWKS_REQUESTS =
|
|
||||||
new SimpleAttributeDefinitionBuilder("min-time-between-jwks-requests", ModelType.INT, true)
|
|
||||||
.setXmlName("min-time-between-jwks-requests")
|
|
||||||
.setValidator(new IntRangeValidator(-1, true))
|
|
||||||
.setAllowExpression(true)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
protected static final List<SimpleAttributeDefinition> DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
|
protected static final List<SimpleAttributeDefinition> DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
|
||||||
static {
|
static {
|
||||||
@@ -107,9 +88,6 @@ class SecureDeploymentDefinition extends SimpleResourceDefinition {
|
|||||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(BEARER_ONLY);
|
DEPLOYMENT_ONLY_ATTRIBUTES.add(BEARER_ONLY);
|
||||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(ENABLE_BASIC_AUTH);
|
DEPLOYMENT_ONLY_ATTRIBUTES.add(ENABLE_BASIC_AUTH);
|
||||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(PUBLIC_CLIENT);
|
DEPLOYMENT_ONLY_ATTRIBUTES.add(PUBLIC_CLIENT);
|
||||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(TURN_OFF_CHANGE_SESSION);
|
|
||||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(TOKEN_MINIMUM_TIME_TO_LIVE);
|
|
||||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(MIN_TIME_BETWEEN_JWKS_REQUESTS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
|
protected static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
|
||||||
|
|||||||
+3
-7
@@ -15,9 +15,9 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
keycloak.subsystem=Keycloak adapter subsystem
|
keycloak.subsystem=Keycloak subsystem
|
||||||
keycloak.subsystem.add=Operation Adds Keycloak adapter subsystem
|
keycloak.subsystem.add=Operation Adds Keycloak subsystem
|
||||||
keycloak.subsystem.remove=Operation removes Keycloak adapter subsystem
|
keycloak.subsystem.remove=Operation removes Keycloak subsystem
|
||||||
keycloak.subsystem.realm=A Keycloak realm.
|
keycloak.subsystem.realm=A Keycloak realm.
|
||||||
keycloak.subsystem.secure-deployment=A deployment secured by Keycloak.
|
keycloak.subsystem.secure-deployment=A deployment secured by Keycloak.
|
||||||
|
|
||||||
@@ -63,7 +63,6 @@ keycloak.secure-deployment.resource=Application name
|
|||||||
keycloak.secure-deployment.use-resource-role-mappings=Use resource level permissions from token
|
keycloak.secure-deployment.use-resource-role-mappings=Use resource level permissions from token
|
||||||
keycloak.secure-deployment.credentials=Adapter credentials
|
keycloak.secure-deployment.credentials=Adapter credentials
|
||||||
keycloak.secure-deployment.bearer-only=Bearer Token Auth only
|
keycloak.secure-deployment.bearer-only=Bearer Token Auth only
|
||||||
keycloak.secure-deployment.enable-basic-auth=Enable Basic Authentication
|
|
||||||
keycloak.secure-deployment.public-client=Public client
|
keycloak.secure-deployment.public-client=Public client
|
||||||
keycloak.secure-deployment.enable-cors=Enable Keycloak CORS support
|
keycloak.secure-deployment.enable-cors=Enable Keycloak CORS support
|
||||||
keycloak.secure-deployment.client-keystore=n/a
|
keycloak.secure-deployment.client-keystore=n/a
|
||||||
@@ -79,9 +78,6 @@ keycloak.secure-deployment.register-node-at-startup=Cluster setting
|
|||||||
keycloak.secure-deployment.register-node-period=how often to re-register node
|
keycloak.secure-deployment.register-node-period=how often to re-register node
|
||||||
keycloak.secure-deployment.token-store=cookie or session storage for auth session data
|
keycloak.secure-deployment.token-store=cookie or session storage for auth session data
|
||||||
keycloak.secure-deployment.principal-attribute=token attribute to use to set Principal name
|
keycloak.secure-deployment.principal-attribute=token attribute to use to set Principal name
|
||||||
keycloak.secure-deployment.turn-off-change-session-id-on-login=The session id is changed by default on a successful login. Change this to true if you want to turn this off
|
|
||||||
keycloak.secure-deployment.token-minimum-time-to-live=The adapter will refresh the token if the current token is expired OR will expire in 'token-minimum-time-to-live' seconds or less
|
|
||||||
keycloak.secure-deployment.min-time-between-jwks-requests=If adapter recognize token signed by unknown public key, it will try to download new public key from keycloak server. However it won't try to download if already tried it in less than 'min-time-between-jwks-requests' seconds
|
|
||||||
|
|
||||||
keycloak.secure-deployment.credential=Credential value
|
keycloak.secure-deployment.credential=Credential value
|
||||||
|
|
||||||
|
|||||||
@@ -105,8 +105,6 @@
|
|||||||
<xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
<xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||||
<xs:element name="enable-basic-auth" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
<xs:element name="enable-basic-auth" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||||
<xs:element name="turn-off-change-session-id-on-login" type="xs:boolean" minOccurs="0" maxOccurs="1" />
|
<xs:element name="turn-off-change-session-id-on-login" type="xs:boolean" minOccurs="0" maxOccurs="1" />
|
||||||
<xs:element name="token-minimum-time-to-live" type="xs:integer" minOccurs="0" maxOccurs="1"/>
|
|
||||||
<xs:element name="min-time-between-jwks-requests" type="xs:integer" minOccurs="0" maxOccurs="1"/>
|
|
||||||
</xs:all>
|
</xs:all>
|
||||||
<xs:attribute name="name" type="xs:string" use="required">
|
<xs:attribute name="name" type="xs:string" use="required">
|
||||||
<xs:annotation>
|
<xs:annotation>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.5.9.Final</version>
|
<version>1.9.0.CR1</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>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.5.9.Final</version>
|
<version>1.9.0.CR1</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+3
-5
@@ -19,11 +19,11 @@ package org.keycloak.adapters.installed;
|
|||||||
|
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.OAuthErrorException;
|
import org.keycloak.OAuthErrorException;
|
||||||
|
import org.keycloak.RSATokenVerifier;
|
||||||
|
import org.keycloak.common.VerificationException;
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||||
import org.keycloak.adapters.ServerRequest;
|
import org.keycloak.adapters.ServerRequest;
|
||||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
|
||||||
import org.keycloak.common.VerificationException;
|
|
||||||
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;
|
||||||
@@ -119,7 +119,6 @@ public class KeycloakInstalled {
|
|||||||
.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)
|
|
||||||
.build().toString();
|
.build().toString();
|
||||||
|
|
||||||
Desktop.getDesktop().browse(new URI(authUrl));
|
Desktop.getDesktop().browse(new URI(authUrl));
|
||||||
@@ -176,7 +175,6 @@ public class KeycloakInstalled {
|
|||||||
.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.SCOPE, OAuth2Constants.SCOPE_OPENID)
|
|
||||||
.build().toString();
|
.build().toString();
|
||||||
|
|
||||||
printer.println("Open the following URL in a browser. After login copy/paste the code back and press <enter>");
|
printer.println("Open the following URL in a browser. After login copy/paste the code back and press <enter>");
|
||||||
@@ -213,7 +211,7 @@ public class KeycloakInstalled {
|
|||||||
refreshToken = tokenResponse.getRefreshToken();
|
refreshToken = tokenResponse.getRefreshToken();
|
||||||
idTokenString = tokenResponse.getIdToken();
|
idTokenString = tokenResponse.getIdToken();
|
||||||
|
|
||||||
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
|
||||||
if (idTokenString != null) {
|
if (idTokenString != null) {
|
||||||
try {
|
try {
|
||||||
JWSInput input = new JWSInput(idTokenString);
|
JWSInput input = new JWSInput(idTokenString);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.5.9.Final</version>
|
<version>1.9.0.CR1</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+3
-2
@@ -20,6 +20,8 @@ package org.keycloak.jaxrs;
|
|||||||
import org.keycloak.KeycloakPrincipal;
|
import org.keycloak.KeycloakPrincipal;
|
||||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||||
import org.keycloak.adapters.AdapterUtils;
|
import org.keycloak.adapters.AdapterUtils;
|
||||||
|
import org.keycloak.adapters.spi.AuthChallenge;
|
||||||
|
import org.keycloak.adapters.spi.AuthOutcome;
|
||||||
import org.keycloak.adapters.AuthenticatedActionsHandler;
|
import org.keycloak.adapters.AuthenticatedActionsHandler;
|
||||||
import org.keycloak.adapters.BasicAuthRequestAuthenticator;
|
import org.keycloak.adapters.BasicAuthRequestAuthenticator;
|
||||||
import org.keycloak.adapters.BearerTokenRequestAuthenticator;
|
import org.keycloak.adapters.BearerTokenRequestAuthenticator;
|
||||||
@@ -29,8 +31,6 @@ import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
|||||||
import org.keycloak.adapters.NodesRegistrationManagement;
|
import org.keycloak.adapters.NodesRegistrationManagement;
|
||||||
import org.keycloak.adapters.PreAuthActionsHandler;
|
import org.keycloak.adapters.PreAuthActionsHandler;
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||||
import org.keycloak.adapters.spi.AuthChallenge;
|
|
||||||
import org.keycloak.adapters.spi.AuthOutcome;
|
|
||||||
import org.keycloak.adapters.spi.UserSessionManagement;
|
import org.keycloak.adapters.spi.UserSessionManagement;
|
||||||
import org.keycloak.common.constants.GenericConstants;
|
import org.keycloak.common.constants.GenericConstants;
|
||||||
|
|
||||||
@@ -40,6 +40,7 @@ import javax.ws.rs.container.ContainerRequestContext;
|
|||||||
import javax.ws.rs.container.PreMatching;
|
import javax.ws.rs.container.PreMatching;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.SecurityContext;
|
import javax.ws.rs.core.SecurityContext;
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|||||||
+10
-14
@@ -17,20 +17,21 @@
|
|||||||
|
|
||||||
package org.keycloak.jaxrs;
|
package org.keycloak.jaxrs;
|
||||||
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
import java.io.InputStream;
|
||||||
import org.keycloak.adapters.OIDCHttpFacade;
|
import java.io.OutputStream;
|
||||||
import org.keycloak.adapters.spi.AuthenticationError;
|
import java.util.List;
|
||||||
import org.keycloak.adapters.spi.LogoutError;
|
import java.util.Map;
|
||||||
import org.keycloak.common.util.HostUtils;
|
|
||||||
|
|
||||||
import javax.security.cert.X509Certificate;
|
import javax.security.cert.X509Certificate;
|
||||||
import javax.ws.rs.container.ContainerRequestContext;
|
import javax.ws.rs.container.ContainerRequestContext;
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.SecurityContext;
|
import javax.ws.rs.core.SecurityContext;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
import org.keycloak.KeycloakSecurityContext;
|
||||||
import java.util.List;
|
import org.keycloak.adapters.OIDCHttpFacade;
|
||||||
import java.util.Map;
|
import org.keycloak.adapters.spi.AuthenticationError;
|
||||||
|
import org.keycloak.adapters.spi.LogoutError;
|
||||||
|
import org.keycloak.common.util.HostUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
@@ -66,11 +67,6 @@ public class JaxrsHttpFacade implements OIDCHttpFacade {
|
|||||||
return requestContext.getUriInfo().getRequestUri().toString();
|
return requestContext.getUriInfo().getRequestUri().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getRelativePath() {
|
|
||||||
return requestContext.getUriInfo().getPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSecure() {
|
public boolean isSecure() {
|
||||||
return securityContext.isSecure();
|
return securityContext.isSecure();
|
||||||
|
|||||||
+4
-4
@@ -20,7 +20,6 @@ package org.keycloak.jaxrs;
|
|||||||
import org.keycloak.AbstractOAuthClient;
|
import org.keycloak.AbstractOAuthClient;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
import org.keycloak.representations.AccessTokenResponse;
|
||||||
import org.keycloak.util.TokenUtil;
|
|
||||||
|
|
||||||
import javax.ws.rs.BadRequestException;
|
import javax.ws.rs.BadRequestException;
|
||||||
import javax.ws.rs.InternalServerErrorException;
|
import javax.ws.rs.InternalServerErrorException;
|
||||||
@@ -86,13 +85,14 @@ public class JaxrsOAuthClient extends AbstractOAuthClient {
|
|||||||
}
|
}
|
||||||
public Response redirect(UriInfo uriInfo, String redirectUri) {
|
public Response redirect(UriInfo uriInfo, String redirectUri) {
|
||||||
String state = getStateCode();
|
String state = getStateCode();
|
||||||
String scopeParam = TokenUtil.attachOIDCScope(scope);
|
|
||||||
|
|
||||||
UriBuilder uriBuilder = UriBuilder.fromUri(authUrl)
|
UriBuilder uriBuilder = UriBuilder.fromUri(authUrl)
|
||||||
.queryParam(OAuth2Constants.CLIENT_ID, clientId)
|
.queryParam(OAuth2Constants.CLIENT_ID, clientId)
|
||||||
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
|
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
|
||||||
.queryParam(OAuth2Constants.STATE, state)
|
.queryParam(OAuth2Constants.STATE, state);
|
||||||
.queryParam(OAuth2Constants.SCOPE, scopeParam);
|
if (scope != null) {
|
||||||
|
uriBuilder.queryParam(OAuth2Constants.SCOPE, scope);
|
||||||
|
}
|
||||||
|
|
||||||
URI url = uriBuilder.build();
|
URI url = uriBuilder.build();
|
||||||
|
|
||||||
|
|||||||
+8
-7
@@ -17,18 +17,19 @@
|
|||||||
|
|
||||||
package org.keycloak.jaxrs;
|
package org.keycloak.jaxrs;
|
||||||
|
|
||||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
|
||||||
import org.keycloak.common.constants.GenericConstants;
|
|
||||||
import org.osgi.framework.BundleContext;
|
|
||||||
|
|
||||||
import javax.annotation.Priority;
|
|
||||||
import javax.ws.rs.Priorities;
|
|
||||||
import javax.ws.rs.container.PreMatching;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Priority;
|
||||||
|
import javax.ws.rs.Priorities;
|
||||||
|
import javax.ws.rs.container.PreMatching;
|
||||||
|
|
||||||
|
import org.keycloak.adapters.KeycloakConfigResolver;
|
||||||
|
import org.keycloak.common.constants.GenericConstants;
|
||||||
|
import org.osgi.framework.BundleContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variant of JaxrsBearerTokenFilter, which can be used to properly use resources from current osgi bundle
|
* Variant of JaxrsBearerTokenFilter, which can be used to properly use resources from current osgi bundle
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.5.9.Final</version>
|
<version>1.9.0.CR1</version>
|
||||||
<relativePath>../../../../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-jetty-adapter-spi</artifactId>
|
<artifactId>keycloak-jetty-adapter-spi</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
<artifactId>keycloak-adapter-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|||||||
+10
-43
@@ -18,8 +18,6 @@
|
|||||||
package org.keycloak.adapters.jetty.core;
|
package org.keycloak.adapters.jetty.core;
|
||||||
|
|
||||||
import org.eclipse.jetty.security.DefaultUserIdentity;
|
import org.eclipse.jetty.security.DefaultUserIdentity;
|
||||||
import org.eclipse.jetty.security.IdentityService;
|
|
||||||
import org.eclipse.jetty.security.LoginService;
|
|
||||||
import org.eclipse.jetty.security.ServerAuthException;
|
import org.eclipse.jetty.security.ServerAuthException;
|
||||||
import org.eclipse.jetty.security.UserAuthentication;
|
import org.eclipse.jetty.security.UserAuthentication;
|
||||||
import org.eclipse.jetty.security.authentication.DeferredAuthentication;
|
import org.eclipse.jetty.security.authentication.DeferredAuthentication;
|
||||||
@@ -27,26 +25,28 @@ import org.eclipse.jetty.security.authentication.FormAuthenticator;
|
|||||||
import org.eclipse.jetty.security.authentication.LoginAuthenticator;
|
import org.eclipse.jetty.security.authentication.LoginAuthenticator;
|
||||||
import org.eclipse.jetty.server.Authentication;
|
import org.eclipse.jetty.server.Authentication;
|
||||||
import org.eclipse.jetty.server.Request;
|
import org.eclipse.jetty.server.Request;
|
||||||
|
import org.eclipse.jetty.server.Response;
|
||||||
import org.eclipse.jetty.server.UserIdentity;
|
import org.eclipse.jetty.server.UserIdentity;
|
||||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||||
|
import org.eclipse.jetty.util.URIUtil;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.KeycloakPrincipal;
|
import org.keycloak.KeycloakPrincipal;
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
import org.keycloak.KeycloakSecurityContext;
|
||||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||||
import org.keycloak.adapters.AdapterTokenStore;
|
import org.keycloak.adapters.AdapterTokenStore;
|
||||||
import org.keycloak.adapters.AdapterUtils;
|
import org.keycloak.adapters.AdapterUtils;
|
||||||
|
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
|
||||||
|
import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
|
||||||
|
import org.keycloak.adapters.spi.AuthChallenge;
|
||||||
|
import org.keycloak.adapters.spi.AuthOutcome;
|
||||||
import org.keycloak.adapters.AuthenticatedActionsHandler;
|
import org.keycloak.adapters.AuthenticatedActionsHandler;
|
||||||
|
import org.keycloak.adapters.spi.HttpFacade;
|
||||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
import org.keycloak.adapters.KeycloakConfigResolver;
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||||
import org.keycloak.adapters.NodesRegistrationManagement;
|
import org.keycloak.adapters.NodesRegistrationManagement;
|
||||||
import org.keycloak.adapters.PreAuthActionsHandler;
|
import org.keycloak.adapters.PreAuthActionsHandler;
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||||
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
|
|
||||||
import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
|
|
||||||
import org.keycloak.adapters.spi.AuthChallenge;
|
|
||||||
import org.keycloak.adapters.spi.AuthOutcome;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
import org.keycloak.constants.AdapterConstants;
|
import org.keycloak.constants.AdapterConstants;
|
||||||
import org.keycloak.enums.TokenStore;
|
import org.keycloak.enums.TokenStore;
|
||||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||||
@@ -59,6 +59,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -134,44 +135,10 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
|
|||||||
return new DefaultUserIdentity(theSubject, principal, theRoles);
|
return new DefaultUserIdentity(theSubject, principal, theRoles);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DummyLoginService implements LoginService {
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserIdentity login(String username, Object credentials) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean validate(UserIdentity user) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IdentityService getIdentityService() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setIdentityService(IdentityService service) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logout(UserIdentity user) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setConfiguration(AuthConfiguration configuration) {
|
public void setConfiguration(AuthConfiguration configuration) {
|
||||||
//super.setConfiguration(configuration);
|
//super.setConfiguration(configuration);
|
||||||
initializeKeycloak();
|
initializeKeycloak();
|
||||||
// need this so that getUserPrincipal does not throw NPE
|
|
||||||
_loginService = new DummyLoginService();
|
|
||||||
String error = configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE);
|
String error = configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE);
|
||||||
setErrorPage(error);
|
setErrorPage(error);
|
||||||
}
|
}
|
||||||
@@ -337,13 +304,13 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
|
|||||||
Authentication authentication = request.getAuthentication();
|
Authentication authentication = request.getAuthentication();
|
||||||
if (!(authentication instanceof KeycloakAuthentication)) {
|
if (!(authentication instanceof KeycloakAuthentication)) {
|
||||||
UserIdentity userIdentity = createIdentity(principal);
|
UserIdentity userIdentity = createIdentity(principal);
|
||||||
authentication = createAuthentication(userIdentity, request);
|
authentication = createAuthentication(userIdentity);
|
||||||
request.setAuthentication(authentication);
|
request.setAuthentication(authentication);
|
||||||
}
|
}
|
||||||
return authentication;
|
return authentication;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Authentication createAuthentication(UserIdentity userIdentity, Request request);
|
protected abstract Authentication createAuthentication(UserIdentity userIdentity);
|
||||||
|
|
||||||
public static abstract class KeycloakAuthentication extends UserAuthentication {
|
public static abstract class KeycloakAuthentication extends UserAuthentication {
|
||||||
public KeycloakAuthentication(String method, UserIdentity userIdentity) {
|
public KeycloakAuthentication(String method, UserIdentity userIdentity) {
|
||||||
|
|||||||
+1
-1
@@ -23,11 +23,11 @@ import org.keycloak.KeycloakPrincipal;
|
|||||||
import org.keycloak.KeycloakSecurityContext;
|
import org.keycloak.KeycloakSecurityContext;
|
||||||
import org.keycloak.adapters.AdapterTokenStore;
|
import org.keycloak.adapters.AdapterTokenStore;
|
||||||
import org.keycloak.adapters.CookieTokenStore;
|
import org.keycloak.adapters.CookieTokenStore;
|
||||||
|
import org.keycloak.adapters.spi.HttpFacade;
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
import org.keycloak.adapters.OidcKeycloakAccount;
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||||
import org.keycloak.adapters.RequestAuthenticator;
|
import org.keycloak.adapters.RequestAuthenticator;
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle storage of token info in cookie. Per-request object.
|
* Handle storage of token info in cookie. Per-request object.
|
||||||
|
|||||||
+1
-1
@@ -23,12 +23,12 @@ import org.keycloak.KeycloakPrincipal;
|
|||||||
import org.keycloak.KeycloakSecurityContext;
|
import org.keycloak.KeycloakSecurityContext;
|
||||||
import org.keycloak.adapters.AdapterTokenStore;
|
import org.keycloak.adapters.AdapterTokenStore;
|
||||||
import org.keycloak.adapters.AdapterUtils;
|
import org.keycloak.adapters.AdapterUtils;
|
||||||
|
import org.keycloak.adapters.spi.HttpFacade;
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
import org.keycloak.adapters.OAuthRequestAuthenticator;
|
import org.keycloak.adapters.OAuthRequestAuthenticator;
|
||||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
import org.keycloak.adapters.OidcKeycloakAccount;
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||||
import org.keycloak.adapters.RequestAuthenticator;
|
import org.keycloak.adapters.RequestAuthenticator;
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
|
|||||||
+1
-1
@@ -21,13 +21,13 @@ import org.eclipse.jetty.server.Request;
|
|||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.KeycloakPrincipal;
|
import org.keycloak.KeycloakPrincipal;
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
import org.keycloak.KeycloakSecurityContext;
|
||||||
|
import org.keycloak.adapters.spi.AdapterSessionStore;
|
||||||
import org.keycloak.adapters.AdapterTokenStore;
|
import org.keycloak.adapters.AdapterTokenStore;
|
||||||
import org.keycloak.adapters.AdapterUtils;
|
import org.keycloak.adapters.AdapterUtils;
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
import org.keycloak.adapters.OidcKeycloakAccount;
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||||
import org.keycloak.adapters.RequestAuthenticator;
|
import org.keycloak.adapters.RequestAuthenticator;
|
||||||
import org.keycloak.adapters.spi.AdapterSessionStore;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.5.9.Final</version>
|
<version>1.9.0.CR1</version>
|
||||||
<relativePath>../../../../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+1
-1
@@ -20,8 +20,8 @@ package org.keycloak.adapters.jetty;
|
|||||||
import org.eclipse.jetty.security.authentication.FormAuthenticator;
|
import org.eclipse.jetty.security.authentication.FormAuthenticator;
|
||||||
import org.eclipse.jetty.server.Request;
|
import org.eclipse.jetty.server.Request;
|
||||||
import org.eclipse.jetty.util.MultiMap;
|
import org.eclipse.jetty.util.MultiMap;
|
||||||
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
|
|
||||||
import org.keycloak.adapters.spi.AdapterSessionStore;
|
import org.keycloak.adapters.spi.AdapterSessionStore;
|
||||||
|
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
|
||||||
import org.keycloak.common.util.MultivaluedHashMap;
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
|
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
|
|||||||
+1
-1
@@ -50,7 +50,7 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Authentication createAuthentication(UserIdentity userIdentity, Request request) {
|
protected Authentication createAuthentication(UserIdentity userIdentity) {
|
||||||
return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
|
return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
|
||||||
@Override
|
@Override
|
||||||
public void logout() {
|
public void logout() {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.5.9.Final</version>
|
<version>1.9.0.CR1</version>
|
||||||
<relativePath>../../../../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+1
-1
@@ -21,8 +21,8 @@ import org.eclipse.jetty.http.HttpMethod;
|
|||||||
import org.eclipse.jetty.security.authentication.FormAuthenticator;
|
import org.eclipse.jetty.security.authentication.FormAuthenticator;
|
||||||
import org.eclipse.jetty.server.Request;
|
import org.eclipse.jetty.server.Request;
|
||||||
import org.eclipse.jetty.util.MultiMap;
|
import org.eclipse.jetty.util.MultiMap;
|
||||||
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
|
|
||||||
import org.keycloak.adapters.spi.AdapterSessionStore;
|
import org.keycloak.adapters.spi.AdapterSessionStore;
|
||||||
|
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
|
||||||
import org.keycloak.common.util.MultivaluedHashMap;
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
|
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
|
|||||||
+1
-1
@@ -52,7 +52,7 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Authentication createAuthentication(UserIdentity userIdentity, Request request) {
|
protected Authentication createAuthentication(UserIdentity userIdentity) {
|
||||||
return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
|
return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
|
||||||
@Override
|
@Override
|
||||||
public void logout() {
|
public void logout() {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.5.9.Final</version>
|
<version>1.9.0.CR1</version>
|
||||||
<relativePath>../../../../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+1
-1
@@ -21,8 +21,8 @@ import org.eclipse.jetty.http.HttpMethod;
|
|||||||
import org.eclipse.jetty.security.authentication.FormAuthenticator;
|
import org.eclipse.jetty.security.authentication.FormAuthenticator;
|
||||||
import org.eclipse.jetty.server.Request;
|
import org.eclipse.jetty.server.Request;
|
||||||
import org.eclipse.jetty.util.MultiMap;
|
import org.eclipse.jetty.util.MultiMap;
|
||||||
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
|
|
||||||
import org.keycloak.adapters.spi.AdapterSessionStore;
|
import org.keycloak.adapters.spi.AdapterSessionStore;
|
||||||
|
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
|
||||||
import org.keycloak.common.util.MultivaluedHashMap;
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
|
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
|
|||||||
+1
-1
@@ -47,7 +47,7 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Authentication createAuthentication(UserIdentity userIdentity, Request request) {
|
protected Authentication createAuthentication(UserIdentity userIdentity) {
|
||||||
return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
|
return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
|
||||||
@Override
|
@Override
|
||||||
public void logout() {
|
public void logout() {
|
||||||
|
|||||||
@@ -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>2.5.9.Final</version>
|
|
||||||
<relativePath>../../../../pom.xml</relativePath>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>keycloak-jetty93-adapter</artifactId>
|
|
||||||
<name>Keycloak Jetty 9.3.x Integration</name>
|
|
||||||
<properties>
|
|
||||||
<jetty9.version>9.3.9.v20160517</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 Jetty93RequestAuthenticator extends JettyRequestAuthenticator {
|
|
||||||
public Jetty93RequestAuthenticator(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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
-69
@@ -1,69 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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 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
|
|
||||||
protected JettyRequestAuthenticator createRequestAuthenticator(Request request, JettyHttpFacade facade,
|
|
||||||
KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
|
|
||||||
return new Jetty93RequestAuthenticator(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>2.5.9.Final</version>
|
<version>1.9.0.CR1</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<name>Keycloak Jetty Integration</name>
|
<name>Keycloak Jetty Integration</name>
|
||||||
@@ -33,8 +33,7 @@
|
|||||||
<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.1</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>2.5.9.Final</version>
|
<version>1.9.0.CR1</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@@ -58,25 +58,6 @@
|
|||||||
<goal>minify</goal>
|
<goal>minify</goal>
|
||||||
</goals>
|
</goals>
|
||||||
</execution>
|
</execution>
|
||||||
<execution>
|
|
||||||
<id>min-authz-js</id>
|
|
||||||
<phase>compile</phase>
|
|
||||||
<configuration>
|
|
||||||
<charset>utf-8</charset>
|
|
||||||
<webappSourceDir>${basedir}/src/main/resources</webappSourceDir>
|
|
||||||
<jsSourceDir>.</jsSourceDir>
|
|
||||||
<jsSourceFiles>
|
|
||||||
<jsSourceFile>keycloak-authz.js</jsSourceFile>
|
|
||||||
</jsSourceFiles>
|
|
||||||
|
|
||||||
<webappTargetDir>${project.build.directory}/classes</webappTargetDir>
|
|
||||||
<jsTargetDir>.</jsTargetDir>
|
|
||||||
<jsFinalFile>keycloak-authz.js</jsFinalFile>
|
|
||||||
</configuration>
|
|
||||||
<goals>
|
|
||||||
<goal>minify</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
|
|||||||
@@ -1,181 +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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function( window, undefined ) {
|
|
||||||
|
|
||||||
var KeycloakAuthorization = function (keycloak) {
|
|
||||||
var _instance = this;
|
|
||||||
this.rpt = null;
|
|
||||||
|
|
||||||
this.init = function () {
|
|
||||||
var request = new XMLHttpRequest();
|
|
||||||
|
|
||||||
request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/.well-known/uma-configuration');
|
|
||||||
request.onreadystatechange = function () {
|
|
||||||
if (request.readyState == 4) {
|
|
||||||
if (request.status == 200) {
|
|
||||||
_instance.config = JSON.parse(request.responseText);
|
|
||||||
} else {
|
|
||||||
console.error('Could not obtain configuration from server.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request.send(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
this.authorize = function (wwwAuthenticateHeader) {
|
|
||||||
this.then = function (onGrant, onDeny, onError) {
|
|
||||||
if (wwwAuthenticateHeader.indexOf('UMA') != -1) {
|
|
||||||
var params = wwwAuthenticateHeader.split(',');
|
|
||||||
|
|
||||||
for (i = 0; i < params.length; i++) {
|
|
||||||
var param = params[i].split('=');
|
|
||||||
|
|
||||||
if (param[0] == 'ticket') {
|
|
||||||
var request = new XMLHttpRequest();
|
|
||||||
|
|
||||||
request.open('POST', _instance.config.rpt_endpoint, true);
|
|
||||||
request.setRequestHeader('Content-Type', 'application/json')
|
|
||||||
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token)
|
|
||||||
|
|
||||||
request.onreadystatechange = function () {
|
|
||||||
if (request.readyState == 4) {
|
|
||||||
var status = request.status;
|
|
||||||
|
|
||||||
if (status >= 200 && status < 300) {
|
|
||||||
var rpt = JSON.parse(request.responseText).rpt;
|
|
||||||
_instance.rpt = rpt;
|
|
||||||
onGrant(rpt);
|
|
||||||
} else if (status == 403) {
|
|
||||||
if (onDeny) {
|
|
||||||
onDeny();
|
|
||||||
} else {
|
|
||||||
console.error('Authorization request was denied by the server.');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (onError) {
|
|
||||||
onError();
|
|
||||||
} else {
|
|
||||||
console.error('Could not obtain authorization data from server.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var ticket = param[1].substring(1, param[1].length - 1).trim();
|
|
||||||
|
|
||||||
request.send(JSON.stringify(
|
|
||||||
{
|
|
||||||
ticket: ticket,
|
|
||||||
rpt: _instance.rpt
|
|
||||||
}
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (wwwAuthenticateHeader.indexOf('KC_ETT') != -1) {
|
|
||||||
var params = wwwAuthenticateHeader.substring('KC_ETT'.length).trim().split(',');
|
|
||||||
var clientId = null;
|
|
||||||
|
|
||||||
for (i = 0; i < params.length; i++) {
|
|
||||||
var param = params[i].split('=');
|
|
||||||
|
|
||||||
if (param[0] == 'realm') {
|
|
||||||
clientId = param[1].substring(1, param[1].length - 1).trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_instance.entitlement(clientId).then(onGrant, onDeny, onError);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains all entitlements from a Keycloak Server based on a give resourceServerId.
|
|
||||||
*/
|
|
||||||
this.entitlement = function (resourceSeververId, entitlementRequest ) {
|
|
||||||
this.then = function (onGrant, onDeny, onError) {
|
|
||||||
var request = new XMLHttpRequest();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
request.onreadystatechange = function () {
|
|
||||||
if (request.readyState == 4) {
|
|
||||||
var status = request.status;
|
|
||||||
|
|
||||||
if (status >= 200 && status < 300) {
|
|
||||||
var rpt = JSON.parse(request.responseText).rpt;
|
|
||||||
_instance.rpt = rpt;
|
|
||||||
onGrant(rpt);
|
|
||||||
} else if (status == 403) {
|
|
||||||
if (onDeny) {
|
|
||||||
onDeny();
|
|
||||||
} else {
|
|
||||||
console.error('Authorization request was denied by the server.');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (onError) {
|
|
||||||
onError();
|
|
||||||
} else {
|
|
||||||
console.error('Could not obtain authorization data from server.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var erJson = null
|
|
||||||
|
|
||||||
if(entitlementRequest) {
|
|
||||||
request.open('POST', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/authz/entitlement/' + resourceSeververId, true);
|
|
||||||
request.setRequestHeader("Content-type", "application/json");
|
|
||||||
erJson = JSON.stringify(entitlementRequest)
|
|
||||||
} else {
|
|
||||||
request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/authz/entitlement/' + resourceSeververId, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token)
|
|
||||||
request.send(erJson);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.init(this);
|
|
||||||
};
|
|
||||||
|
|
||||||
if ( typeof module === "object" && module && typeof module.exports === "object" ) {
|
|
||||||
module.exports = KeycloakAuthorization;
|
|
||||||
} else {
|
|
||||||
window.KeycloakAuthorization = KeycloakAuthorization;
|
|
||||||
|
|
||||||
if ( typeof define === "function" && define.amd ) {
|
|
||||||
define( "keycloak-authorization", [], function () { return KeycloakAuthorization; } );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})( window );
|
|
||||||
@@ -25,29 +25,20 @@
|
|||||||
var kc = this;
|
var kc = this;
|
||||||
var adapter;
|
var adapter;
|
||||||
var refreshQueue = [];
|
var refreshQueue = [];
|
||||||
var callbackStorage;
|
|
||||||
|
|
||||||
var loginIframe = {
|
var loginIframe = {
|
||||||
enable: true,
|
enable: true,
|
||||||
callbackList: [],
|
callbackMap: [],
|
||||||
interval: 5
|
interval: 5
|
||||||
};
|
};
|
||||||
|
|
||||||
kc.init = function (initOptions) {
|
kc.init = function (initOptions) {
|
||||||
kc.authenticated = false;
|
kc.authenticated = false;
|
||||||
|
|
||||||
callbackStorage = createCallbackStorage();
|
if (window.Cordova) {
|
||||||
|
|
||||||
if (initOptions && initOptions.adapter === 'cordova') {
|
|
||||||
adapter = loadAdapter('cordova');
|
adapter = loadAdapter('cordova');
|
||||||
} else if (initOptions && initOptions.adapter === 'default') {
|
|
||||||
adapter = loadAdapter();
|
|
||||||
} else {
|
} else {
|
||||||
if (window.Cordova) {
|
adapter = loadAdapter();
|
||||||
adapter = loadAdapter('cordova');
|
|
||||||
} else {
|
|
||||||
adapter = loadAdapter();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (initOptions) {
|
if (initOptions) {
|
||||||
@@ -87,10 +78,6 @@
|
|||||||
}
|
}
|
||||||
kc.flow = initOptions.flow;
|
kc.flow = initOptions.flow;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (initOptions.timeSkew != null) {
|
|
||||||
kc.timeSkew = initOptions.timeSkew;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!kc.responseMode) {
|
if (!kc.responseMode) {
|
||||||
@@ -107,8 +94,8 @@
|
|||||||
initPromise.promise.success(function() {
|
initPromise.promise.success(function() {
|
||||||
kc.onReady && kc.onReady(kc.authenticated);
|
kc.onReady && kc.onReady(kc.authenticated);
|
||||||
promise.setSuccess(kc.authenticated);
|
promise.setSuccess(kc.authenticated);
|
||||||
}).error(function(errorData) {
|
}).error(function() {
|
||||||
promise.setError(errorData);
|
promise.setError();
|
||||||
});
|
});
|
||||||
|
|
||||||
var configPromise = loadConfig(config);
|
var configPromise = loadConfig(config);
|
||||||
@@ -157,36 +144,25 @@
|
|||||||
processCallback(callback, initPromise);
|
processCallback(callback, initPromise);
|
||||||
return;
|
return;
|
||||||
} else if (initOptions) {
|
} else if (initOptions) {
|
||||||
if (initOptions.refreshToken) {
|
if (initOptions.token || initOptions.refreshToken) {
|
||||||
setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken);
|
setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken, false);
|
||||||
|
kc.timeSkew = initOptions.timeSkew || 0;
|
||||||
|
|
||||||
if (loginIframe.enable) {
|
if (loginIframe.enable) {
|
||||||
setupCheckLoginIframe().success(function() {
|
setupCheckLoginIframe().success(function() {
|
||||||
checkLoginIframe().success(function () {
|
checkLoginIframe().success(function () {
|
||||||
kc.onAuthSuccess && kc.onAuthSuccess();
|
|
||||||
initPromise.setSuccess();
|
initPromise.setSuccess();
|
||||||
}).error(function () {
|
}).error(function () {
|
||||||
setToken(null, null, null);
|
if (initOptions.onLoad) {
|
||||||
initPromise.setSuccess();
|
onLoad();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
kc.updateToken(-1).success(function() {
|
initPromise.setSuccess();
|
||||||
kc.onAuthSuccess && kc.onAuthSuccess();
|
|
||||||
initPromise.setSuccess();
|
|
||||||
}).error(function() {
|
|
||||||
kc.onAuthError && kc.onAuthError();
|
|
||||||
if (initOptions.onLoad) {
|
|
||||||
onLoad();
|
|
||||||
} else {
|
|
||||||
initPromise.setError();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else if (initOptions.onLoad) {
|
} else if (initOptions.onLoad) {
|
||||||
onLoad();
|
onLoad();
|
||||||
} else {
|
|
||||||
initPromise.setSuccess();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
initPromise.setSuccess();
|
initPromise.setSuccess();
|
||||||
@@ -210,26 +186,17 @@
|
|||||||
var nonce = createUUID();
|
var nonce = createUUID();
|
||||||
|
|
||||||
var redirectUri = adapter.redirectUri(options);
|
var redirectUri = adapter.redirectUri(options);
|
||||||
|
|
||||||
var callbackState = {
|
|
||||||
state: state,
|
|
||||||
nonce: nonce,
|
|
||||||
redirectUri: encodeURIComponent(redirectUri),
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options && options.prompt) {
|
if (options && options.prompt) {
|
||||||
callbackState.prompt = options.prompt;
|
redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'prompt=' + options.prompt;
|
||||||
}
|
}
|
||||||
|
|
||||||
callbackStorage.add(callbackState);
|
sessionStorage.oauthState = JSON.stringify({ state: state, nonce: nonce, redirectUri: encodeURIComponent(redirectUri) });
|
||||||
|
|
||||||
var action = 'auth';
|
var action = 'auth';
|
||||||
if (options && options.action == 'register') {
|
if (options && options.action == 'register') {
|
||||||
action = 'registrations';
|
action = 'registrations';
|
||||||
}
|
}
|
||||||
|
|
||||||
var scope = (options && options.scope) ? "openid " + options.scope : "openid";
|
|
||||||
|
|
||||||
var url = getRealmUrl()
|
var url = getRealmUrl()
|
||||||
+ '/protocol/openid-connect/' + action
|
+ '/protocol/openid-connect/' + action
|
||||||
+ '?client_id=' + encodeURIComponent(kc.clientId)
|
+ '?client_id=' + encodeURIComponent(kc.clientId)
|
||||||
@@ -237,17 +204,12 @@
|
|||||||
+ '&state=' + encodeURIComponent(state)
|
+ '&state=' + encodeURIComponent(state)
|
||||||
+ '&nonce=' + encodeURIComponent(nonce)
|
+ '&nonce=' + encodeURIComponent(nonce)
|
||||||
+ '&response_mode=' + encodeURIComponent(kc.responseMode)
|
+ '&response_mode=' + encodeURIComponent(kc.responseMode)
|
||||||
+ '&response_type=' + encodeURIComponent(kc.responseType)
|
+ '&response_type=' + encodeURIComponent(kc.responseType);
|
||||||
+ '&scope=' + encodeURIComponent(scope);
|
|
||||||
|
|
||||||
if (options && options.prompt) {
|
if (options && options.prompt) {
|
||||||
url += '&prompt=' + encodeURIComponent(options.prompt);
|
url += '&prompt=' + encodeURIComponent(options.prompt);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options && options.maxAge) {
|
|
||||||
url += '&max_age=' + encodeURIComponent(options.maxAge);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options && options.loginHint) {
|
if (options && options.loginHint) {
|
||||||
url += '&login_hint=' + encodeURIComponent(options.loginHint);
|
url += '&login_hint=' + encodeURIComponent(options.loginHint);
|
||||||
}
|
}
|
||||||
@@ -256,6 +218,10 @@
|
|||||||
url += '&kc_idp_hint=' + encodeURIComponent(options.idpHint);
|
url += '&kc_idp_hint=' + encodeURIComponent(options.idpHint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options && options.scope) {
|
||||||
|
url += '&scope=' + encodeURIComponent(options.scope);
|
||||||
|
}
|
||||||
|
|
||||||
if (options && options.locale) {
|
if (options && options.locale) {
|
||||||
url += '&ui_locales=' + encodeURIComponent(options.locale);
|
url += '&ui_locales=' + encodeURIComponent(options.locale);
|
||||||
}
|
}
|
||||||
@@ -270,7 +236,7 @@
|
|||||||
kc.createLogoutUrl = function(options) {
|
kc.createLogoutUrl = function(options) {
|
||||||
var url = getRealmUrl()
|
var url = getRealmUrl()
|
||||||
+ '/protocol/openid-connect/logout'
|
+ '/protocol/openid-connect/logout'
|
||||||
+ '?redirect_uri=' + encodeURIComponent(adapter.redirectUri(options, false));
|
+ '?redirect_uri=' + encodeURIComponent(adapter.redirectUri(options));
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
@@ -369,22 +335,18 @@
|
|||||||
throw 'Not authenticated';
|
throw 'Not authenticated';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kc.timeSkew == null) {
|
var expiresIn = kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew;
|
||||||
console.info('[KEYCLOAK] Unable to determine if token is expired as timeskew is not set');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var expiresIn = kc.tokenParsed['exp'] - Math.ceil(new Date().getTime() / 1000) + kc.timeSkew;
|
|
||||||
if (minValidity) {
|
if (minValidity) {
|
||||||
expiresIn -= minValidity;
|
expiresIn -= minValidity;
|
||||||
}
|
}
|
||||||
|
|
||||||
return expiresIn < 0;
|
return expiresIn < 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
kc.updateToken = function(minValidity) {
|
kc.updateToken = function(minValidity) {
|
||||||
var promise = createPromise();
|
var promise = createPromise();
|
||||||
|
|
||||||
if (!kc.refreshToken) {
|
if (!kc.tokenParsed || !kc.refreshToken) {
|
||||||
promise.setError();
|
promise.setError();
|
||||||
return promise.promise;
|
return promise.promise;
|
||||||
}
|
}
|
||||||
@@ -392,16 +354,7 @@
|
|||||||
minValidity = minValidity || 5;
|
minValidity = minValidity || 5;
|
||||||
|
|
||||||
var exec = function() {
|
var exec = function() {
|
||||||
var refreshToken = false;
|
if (!kc.isTokenExpired(minValidity)) {
|
||||||
if (minValidity == -1) {
|
|
||||||
refreshToken = true;
|
|
||||||
console.info('[KEYCLOAK] Refreshing token: forced refresh');
|
|
||||||
} else if (!kc.tokenParsed || kc.isTokenExpired(minValidity)) {
|
|
||||||
refreshToken = true;
|
|
||||||
console.info('[KEYCLOAK] Refreshing token: token expired');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!refreshToken) {
|
|
||||||
promise.setSuccess(false);
|
promise.setSuccess(false);
|
||||||
} else {
|
} else {
|
||||||
var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken;
|
var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken;
|
||||||
@@ -413,7 +366,6 @@
|
|||||||
var req = new XMLHttpRequest();
|
var req = new XMLHttpRequest();
|
||||||
req.open('POST', url, true);
|
req.open('POST', url, true);
|
||||||
req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
||||||
req.withCredentials = true;
|
|
||||||
|
|
||||||
if (kc.clientId && kc.clientSecret) {
|
if (kc.clientId && kc.clientSecret) {
|
||||||
req.setRequestHeader('Authorization', 'Basic ' + btoa(kc.clientId + ':' + kc.clientSecret));
|
req.setRequestHeader('Authorization', 'Basic ' + btoa(kc.clientId + ':' + kc.clientSecret));
|
||||||
@@ -426,21 +378,18 @@
|
|||||||
req.onreadystatechange = function () {
|
req.onreadystatechange = function () {
|
||||||
if (req.readyState == 4) {
|
if (req.readyState == 4) {
|
||||||
if (req.status == 200) {
|
if (req.status == 200) {
|
||||||
console.info('[KEYCLOAK] Token refreshed');
|
|
||||||
|
|
||||||
timeLocal = (timeLocal + new Date().getTime()) / 2;
|
timeLocal = (timeLocal + new Date().getTime()) / 2;
|
||||||
|
|
||||||
var tokenResponse = JSON.parse(req.responseText);
|
var tokenResponse = JSON.parse(req.responseText);
|
||||||
|
setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], true);
|
||||||
|
|
||||||
setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], timeLocal);
|
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
|
||||||
|
|
||||||
kc.onAuthRefreshSuccess && kc.onAuthRefreshSuccess();
|
kc.onAuthRefreshSuccess && kc.onAuthRefreshSuccess();
|
||||||
for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
|
for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
|
||||||
p.setSuccess(true);
|
p.setSuccess(true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn('[KEYCLOAK] Failed to refresh token');
|
|
||||||
|
|
||||||
kc.onAuthRefreshError && kc.onAuthRefreshError();
|
kc.onAuthRefreshError && kc.onAuthRefreshError();
|
||||||
for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
|
for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
|
||||||
p.setError(true);
|
p.setError(true);
|
||||||
@@ -470,7 +419,7 @@
|
|||||||
|
|
||||||
kc.clearToken = function() {
|
kc.clearToken = function() {
|
||||||
if (kc.token) {
|
if (kc.token) {
|
||||||
setToken(null, null, null);
|
setToken(null, null, null, true);
|
||||||
kc.onAuthLogout && kc.onAuthLogout();
|
kc.onAuthLogout && kc.onAuthLogout();
|
||||||
if (kc.loginRequired) {
|
if (kc.loginRequired) {
|
||||||
kc.login();
|
kc.login();
|
||||||
@@ -503,9 +452,8 @@
|
|||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
if (prompt != 'none') {
|
if (prompt != 'none') {
|
||||||
var errorData = { error: error, error_description: oauth.error_description };
|
kc.onAuthError && kc.onAuthError();
|
||||||
kc.onAuthError && kc.onAuthError(errorData);
|
promise && promise.setError();
|
||||||
promise && promise.setError(errorData);
|
|
||||||
} else {
|
} else {
|
||||||
promise && promise.setSuccess();
|
promise && promise.setSuccess();
|
||||||
}
|
}
|
||||||
@@ -551,16 +499,18 @@
|
|||||||
function authSuccess(accessToken, refreshToken, idToken, fulfillPromise) {
|
function authSuccess(accessToken, refreshToken, idToken, fulfillPromise) {
|
||||||
timeLocal = (timeLocal + new Date().getTime()) / 2;
|
timeLocal = (timeLocal + new Date().getTime()) / 2;
|
||||||
|
|
||||||
setToken(accessToken, refreshToken, idToken, timeLocal);
|
setToken(accessToken, refreshToken, idToken, true);
|
||||||
|
|
||||||
if ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) ||
|
if ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) ||
|
||||||
(kc.refreshTokenParsed && kc.refreshTokenParsed.nonce != oauth.storedNonce) ||
|
(kc.refreshTokenParsed && kc.refreshTokenParsed.nonce != oauth.storedNonce) ||
|
||||||
(kc.idTokenParsed && kc.idTokenParsed.nonce != oauth.storedNonce)) {
|
(kc.idTokenParsed && kc.idTokenParsed.nonce != oauth.storedNonce)) {
|
||||||
|
|
||||||
console.info('[KEYCLOAK] Invalid nonce, clearing token');
|
console.log('invalid nonce!');
|
||||||
kc.clearToken();
|
kc.clearToken();
|
||||||
promise && promise.setError();
|
promise && promise.setError();
|
||||||
} else {
|
} else {
|
||||||
|
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
|
||||||
|
|
||||||
if (fulfillPromise) {
|
if (fulfillPromise) {
|
||||||
kc.onAuthSuccess && kc.onAuthSuccess();
|
kc.onAuthSuccess && kc.onAuthSuccess();
|
||||||
promise && promise.setSuccess();
|
promise && promise.setSuccess();
|
||||||
@@ -633,12 +583,41 @@
|
|||||||
return promise.promise;
|
return promise.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setToken(token, refreshToken, idToken, timeLocal) {
|
function setToken(token, refreshToken, idToken, useTokenTime) {
|
||||||
if (kc.tokenTimeoutHandle) {
|
if (kc.tokenTimeoutHandle) {
|
||||||
clearTimeout(kc.tokenTimeoutHandle);
|
clearTimeout(kc.tokenTimeoutHandle);
|
||||||
kc.tokenTimeoutHandle = null;
|
kc.tokenTimeoutHandle = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
kc.token = token;
|
||||||
|
kc.tokenParsed = decodeToken(token);
|
||||||
|
var sessionId = kc.realm + '/' + kc.tokenParsed.sub;
|
||||||
|
if (kc.tokenParsed.session_state) {
|
||||||
|
sessionId = sessionId + '/' + kc.tokenParsed.session_state;
|
||||||
|
}
|
||||||
|
kc.sessionId = sessionId;
|
||||||
|
kc.authenticated = true;
|
||||||
|
kc.subject = kc.tokenParsed.sub;
|
||||||
|
kc.realmAccess = kc.tokenParsed.realm_access;
|
||||||
|
kc.resourceAccess = kc.tokenParsed.resource_access;
|
||||||
|
|
||||||
|
if (kc.onTokenExpired) {
|
||||||
|
var start = useTokenTime ? kc.tokenParsed.iat : (new Date().getTime() / 1000);
|
||||||
|
var expiresIn = kc.tokenParsed.exp - start;
|
||||||
|
kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
delete kc.token;
|
||||||
|
delete kc.tokenParsed;
|
||||||
|
delete kc.subject;
|
||||||
|
delete kc.realmAccess;
|
||||||
|
delete kc.resourceAccess;
|
||||||
|
|
||||||
|
kc.authenticated = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (refreshToken) {
|
if (refreshToken) {
|
||||||
kc.refreshToken = refreshToken;
|
kc.refreshToken = refreshToken;
|
||||||
kc.refreshTokenParsed = decodeToken(refreshToken);
|
kc.refreshTokenParsed = decodeToken(refreshToken);
|
||||||
@@ -654,42 +633,6 @@
|
|||||||
delete kc.idToken;
|
delete kc.idToken;
|
||||||
delete kc.idTokenParsed;
|
delete kc.idTokenParsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (token) {
|
|
||||||
kc.token = token;
|
|
||||||
kc.tokenParsed = decodeToken(token);
|
|
||||||
kc.sessionId = kc.tokenParsed.session_state;
|
|
||||||
kc.authenticated = true;
|
|
||||||
kc.subject = kc.tokenParsed.sub;
|
|
||||||
kc.realmAccess = kc.tokenParsed.realm_access;
|
|
||||||
kc.resourceAccess = kc.tokenParsed.resource_access;
|
|
||||||
|
|
||||||
if (timeLocal) {
|
|
||||||
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (kc.timeSkew != null) {
|
|
||||||
console.info('[KEYCLOAK] Estimated time difference between browser and server is ' + kc.timeSkew + ' seconds');
|
|
||||||
|
|
||||||
if (kc.onTokenExpired) {
|
|
||||||
var expiresIn = (kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew) * 1000;
|
|
||||||
console.info('[KEYCLOAK] Token expires in ' + Math.round(expiresIn / 1000) + ' s');
|
|
||||||
if (expiresIn <= 0) {
|
|
||||||
kc.onTokenExpired();
|
|
||||||
} else {
|
|
||||||
kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
delete kc.token;
|
|
||||||
delete kc.tokenParsed;
|
|
||||||
delete kc.subject;
|
|
||||||
delete kc.realmAccess;
|
|
||||||
delete kc.resourceAccess;
|
|
||||||
|
|
||||||
kc.authenticated = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function decodeToken(str) {
|
function decodeToken(str) {
|
||||||
@@ -743,12 +686,14 @@
|
|||||||
|
|
||||||
function parseCallback(url) {
|
function parseCallback(url) {
|
||||||
var oauth = new CallbackParser(url, kc.responseMode).parseUri();
|
var oauth = new CallbackParser(url, kc.responseMode).parseUri();
|
||||||
var oauthState = callbackStorage.get(oauth.state);
|
|
||||||
|
|
||||||
if (oauthState && (oauth.code || oauth.error || oauth.access_token || oauth.id_token)) {
|
var sessionState = sessionStorage.oauthState && JSON.parse(sessionStorage.oauthState);
|
||||||
oauth.redirectUri = oauthState.redirectUri;
|
|
||||||
oauth.storedNonce = oauthState.nonce;
|
if (sessionState && (oauth.code || oauth.error || oauth.access_token || oauth.id_token) && oauth.state && oauth.state == sessionState.state) {
|
||||||
oauth.prompt = oauthState.prompt;
|
delete sessionStorage.oauthState;
|
||||||
|
|
||||||
|
oauth.redirectUri = sessionState.redirectUri;
|
||||||
|
oauth.storedNonce = sessionState.nonce;
|
||||||
|
|
||||||
if (oauth.fragment) {
|
if (oauth.fragment) {
|
||||||
oauth.newUrl += '#' + oauth.fragment;
|
oauth.newUrl += '#' + oauth.fragment;
|
||||||
@@ -826,37 +771,26 @@
|
|||||||
setTimeout(check, loginIframe.interval * 1000);
|
setTimeout(check, loginIframe.interval * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
var src = getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html';
|
var src = getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html?client_id=' + encodeURIComponent(kc.clientId) + '&origin=' + getOrigin();
|
||||||
iframe.setAttribute('src', src );
|
iframe.setAttribute('src', src );
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
var data = JSON.parse(event.data);
|
||||||
|
var promise = loginIframe.callbackMap[data.callbackId];
|
||||||
|
delete loginIframe.callbackMap[data.callbackId];
|
||||||
|
|
||||||
if (!(event.data == 'unchanged' || event.data == 'changed' || event.data == 'error')) {
|
if ((!kc.sessionId || kc.sessionId == data.session) && data.loggedIn) {
|
||||||
return;
|
promise.setSuccess();
|
||||||
}
|
} else {
|
||||||
|
|
||||||
|
|
||||||
if (event.data != 'unchanged') {
|
|
||||||
kc.clearToken();
|
kc.clearToken();
|
||||||
}
|
promise.setError();
|
||||||
|
|
||||||
var callbacks = loginIframe.callbackList.splice(0, loginIframe.callbackList.length);
|
|
||||||
|
|
||||||
for (var i = callbacks.length - 1; i >= 0; --i) {
|
|
||||||
var promise = callbacks[i];
|
|
||||||
if (event.data == 'unchanged') {
|
|
||||||
promise.setSuccess();
|
|
||||||
} else {
|
|
||||||
promise.setError();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('message', messageCallback, false);
|
window.addEventListener('message', messageCallback, false);
|
||||||
|
|
||||||
var check = function() {
|
var check = function() {
|
||||||
@@ -872,13 +806,12 @@
|
|||||||
function checkLoginIframe() {
|
function checkLoginIframe() {
|
||||||
var promise = createPromise();
|
var promise = createPromise();
|
||||||
|
|
||||||
if (loginIframe.iframe && loginIframe.iframeOrigin ) {
|
if (loginIframe.iframe && loginIframe.iframeOrigin) {
|
||||||
var msg = kc.clientId + ' ' + kc.sessionId;
|
var msg = {};
|
||||||
loginIframe.callbackList.push(promise);
|
msg.callbackId = createCallbackId();
|
||||||
|
loginIframe.callbackMap[msg.callbackId] = promise;
|
||||||
var origin = loginIframe.iframeOrigin;
|
var origin = loginIframe.iframeOrigin;
|
||||||
if (loginIframe.callbackList.length == 1) {
|
loginIframe.iframe.contentWindow.postMessage(JSON.stringify(msg), origin);
|
||||||
loginIframe.iframe.contentWindow.postMessage(msg, origin);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
promise.setSuccess();
|
promise.setSuccess();
|
||||||
}
|
}
|
||||||
@@ -909,18 +842,14 @@
|
|||||||
return createPromise().promise;
|
return createPromise().promise;
|
||||||
},
|
},
|
||||||
|
|
||||||
redirectUri: function(options, encodeHash) {
|
redirectUri: function(options) {
|
||||||
if (arguments.length == 1) {
|
|
||||||
encodeHash = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options && options.redirectUri) {
|
if (options && options.redirectUri) {
|
||||||
return options.redirectUri;
|
return options.redirectUri;
|
||||||
} else if (kc.redirectUri) {
|
} else if (kc.redirectUri) {
|
||||||
return kc.redirectUri;
|
return kc.redirectUri;
|
||||||
} else {
|
} else {
|
||||||
var redirectUri = location.href;
|
var redirectUri = location.href;
|
||||||
if (location.hash && encodeHash) {
|
if (location.hash) {
|
||||||
redirectUri = redirectUri.substring(0, location.href.indexOf('#'));
|
redirectUri = redirectUri.substring(0, location.href.indexOf('#'));
|
||||||
redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'redirect_fragment=' + encodeURIComponent(location.hash.substring(1));
|
redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'redirect_fragment=' + encodeURIComponent(location.hash.substring(1));
|
||||||
}
|
}
|
||||||
@@ -945,28 +874,31 @@
|
|||||||
var loginUrl = kc.createLoginUrl(options);
|
var loginUrl = kc.createLoginUrl(options);
|
||||||
var ref = window.open(loginUrl, '_blank', o);
|
var ref = window.open(loginUrl, '_blank', o);
|
||||||
|
|
||||||
var completed = false;
|
var callback;
|
||||||
|
var error;
|
||||||
|
|
||||||
ref.addEventListener('loadstart', function(event) {
|
ref.addEventListener('loadstart', function(event) {
|
||||||
if (event.url.indexOf('http://localhost') == 0) {
|
if (event.url.indexOf('http://localhost') == 0) {
|
||||||
var callback = parseCallback(event.url);
|
callback = parseCallback(event.url);
|
||||||
processCallback(callback, promise);
|
|
||||||
ref.close();
|
ref.close();
|
||||||
completed = true;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ref.addEventListener('loaderror', function(event) {
|
ref.addEventListener('loaderror', function(event) {
|
||||||
if (!completed) {
|
if (event.url.indexOf('http://localhost') == 0) {
|
||||||
if (event.url.indexOf('http://localhost') == 0) {
|
callback = parseCallback(event.url);
|
||||||
var callback = parseCallback(event.url);
|
ref.close();
|
||||||
processCallback(callback, promise);
|
} else {
|
||||||
ref.close();
|
error = true;
|
||||||
completed = true;
|
ref.close();
|
||||||
} else {
|
}
|
||||||
promise.setError();
|
});
|
||||||
ref.close();
|
|
||||||
}
|
ref.addEventListener('exit', function(event) {
|
||||||
|
if (error || !callback) {
|
||||||
|
promise.setError();
|
||||||
|
} else {
|
||||||
|
processCallback(callback, promise);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1037,124 +969,6 @@
|
|||||||
throw 'invalid adapter type: ' + type;
|
throw 'invalid adapter type: ' + type;
|
||||||
}
|
}
|
||||||
|
|
||||||
var LocalStorage = function() {
|
|
||||||
if (!(this instanceof LocalStorage)) {
|
|
||||||
return new LocalStorage();
|
|
||||||
}
|
|
||||||
|
|
||||||
localStorage.setItem('kc-test', 'test');
|
|
||||||
localStorage.removeItem('kc-test');
|
|
||||||
|
|
||||||
var cs = this;
|
|
||||||
|
|
||||||
function clearExpired() {
|
|
||||||
var time = new Date().getTime();
|
|
||||||
for (var i = 0; i < localStorage.length; i++) {
|
|
||||||
var key = localStorage.key(i);
|
|
||||||
if (key && key.indexOf('kc-callback-') == 0) {
|
|
||||||
var value = localStorage.getItem(key);
|
|
||||||
if (value) {
|
|
||||||
try {
|
|
||||||
var expires = JSON.parse(value).expires;
|
|
||||||
if (!expires || expires < time) {
|
|
||||||
localStorage.removeItem(key);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
localStorage.removeItem(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cs.get = function(state) {
|
|
||||||
if (!state) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var key = 'kc-callback-' + state;
|
|
||||||
var value = localStorage.getItem(key);
|
|
||||||
if (value) {
|
|
||||||
localStorage.removeItem(key);
|
|
||||||
value = JSON.parse(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearExpired();
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|
||||||
cs.add = function(state) {
|
|
||||||
clearExpired();
|
|
||||||
|
|
||||||
var key = 'kc-callback-' + state.state;
|
|
||||||
state.expires = new Date().getTime() + (60 * 60 * 1000);
|
|
||||||
localStorage.setItem(key, JSON.stringify(state));
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
var CookieStorage = function() {
|
|
||||||
if (!(this instanceof CookieStorage)) {
|
|
||||||
return new CookieStorage();
|
|
||||||
}
|
|
||||||
|
|
||||||
var cs = this;
|
|
||||||
|
|
||||||
cs.get = function(state) {
|
|
||||||
if (!state) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var value = getCookie('kc-callback-' + state);
|
|
||||||
setCookie('kc-callback-' + state, '', cookieExpiration(-100));
|
|
||||||
if (value) {
|
|
||||||
return JSON.parse(value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
cs.add = function(state) {
|
|
||||||
setCookie('kc-callback-' + state.state, JSON.stringify(state), cookieExpiration(60));
|
|
||||||
};
|
|
||||||
|
|
||||||
cs.removeItem = function(key) {
|
|
||||||
setCookie(key, '', cookieExpiration(-100));
|
|
||||||
};
|
|
||||||
|
|
||||||
var cookieExpiration = function (minutes) {
|
|
||||||
var exp = new Date();
|
|
||||||
exp.setTime(exp.getTime() + (minutes*60*1000));
|
|
||||||
return exp;
|
|
||||||
};
|
|
||||||
|
|
||||||
var getCookie = function (key) {
|
|
||||||
var name = key + '=';
|
|
||||||
var ca = document.cookie.split(';');
|
|
||||||
for (var i = 0; i < ca.length; i++) {
|
|
||||||
var c = ca[i];
|
|
||||||
while (c.charAt(0) == ' ') {
|
|
||||||
c = c.substring(1);
|
|
||||||
}
|
|
||||||
if (c.indexOf(name) == 0) {
|
|
||||||
return c.substring(name.length, c.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
};
|
|
||||||
|
|
||||||
var setCookie = function (key, value, expirationDate) {
|
|
||||||
var cookie = key + '=' + value + '; '
|
|
||||||
+ 'expires=' + expirationDate.toUTCString() + '; ';
|
|
||||||
document.cookie = cookie;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function createCallbackStorage() {
|
|
||||||
try {
|
|
||||||
return new LocalStorage();
|
|
||||||
} catch (err) {
|
|
||||||
}
|
|
||||||
|
|
||||||
return new CookieStorage();
|
|
||||||
}
|
|
||||||
|
|
||||||
var CallbackParser = function(uriToParse, responseMode) {
|
var CallbackParser = function(uriToParse, responseMode) {
|
||||||
if (!(this instanceof CallbackParser)) {
|
if (!(this instanceof CallbackParser)) {
|
||||||
@@ -1200,7 +1014,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var handleQueryParam = function(paramName, paramValue, oauth) {
|
var handleQueryParam = function(paramName, paramValue, oauth) {
|
||||||
var supportedOAuthParams = [ 'code', 'state', 'error', 'error_description' ];
|
var supportedOAuthParams = [ 'code', 'error', 'state' ];
|
||||||
|
|
||||||
for (var i = 0 ; i< supportedOAuthParams.length ; i++) {
|
for (var i = 0 ; i< supportedOAuthParams.length ; i++) {
|
||||||
if (paramName === supportedOAuthParams[i]) {
|
if (paramName === supportedOAuthParams[i]) {
|
||||||
@@ -1226,6 +1040,9 @@
|
|||||||
case 'redirect_fragment':
|
case 'redirect_fragment':
|
||||||
oauth.fragment = queryParams[param];
|
oauth.fragment = queryParams[param];
|
||||||
break;
|
break;
|
||||||
|
case 'prompt':
|
||||||
|
oauth.prompt = queryParams[param];
|
||||||
|
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 + '=' + queryParams[param];
|
oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + param + '=' + queryParams[param];
|
||||||
|
|||||||
@@ -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.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<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>
|
|
||||||
@@ -15,85 +15,35 @@
|
|||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<script>
|
<script>
|
||||||
var init;
|
function getCookie(cname)
|
||||||
|
|
||||||
function checkState(clientId, origin, sessionState, callback) {
|
|
||||||
var cookie = getCookie();
|
|
||||||
|
|
||||||
if (!cookie) {
|
|
||||||
callback('changed');
|
|
||||||
} else if (!init) {
|
|
||||||
var req = new XMLHttpRequest();
|
|
||||||
|
|
||||||
var url = location.href + "/init";
|
|
||||||
url += "?client_id=" + encodeURIComponent(clientId);
|
|
||||||
url += "&origin=" + encodeURIComponent(origin);
|
|
||||||
|
|
||||||
req.open('GET', url, true);
|
|
||||||
|
|
||||||
req.onreadystatechange = function () {
|
|
||||||
if (req.readyState === 4) {
|
|
||||||
if (req.status === 204) {
|
|
||||||
init = {
|
|
||||||
clientId: clientId,
|
|
||||||
origin: origin
|
|
||||||
}
|
|
||||||
callback('unchanged');
|
|
||||||
} else if (req.status === 404) {
|
|
||||||
callback('changed');
|
|
||||||
} else {
|
|
||||||
callback('error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
req.send();
|
|
||||||
} else {
|
|
||||||
if (clientId === init.clientId && origin === init.origin) {
|
|
||||||
var c = cookie.split('/');
|
|
||||||
if (sessionState === c[2]) {
|
|
||||||
callback('unchanged');
|
|
||||||
} else {
|
|
||||||
callback('changed');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
callback('error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCookie()
|
|
||||||
{
|
{
|
||||||
var name = 'KEYCLOAK_SESSION=';
|
var name = cname + "=";
|
||||||
var ca = document.cookie.split(';');
|
var ca = document.cookie.split(';');
|
||||||
for(var i=0; i<ca.length; i++)
|
for(var i=0; i<ca.length; i++)
|
||||||
{
|
{
|
||||||
var c = ca[i].trim();
|
var c = ca[i].trim();
|
||||||
if (c.indexOf(name)===0) return c.substring(name.length,c.length);
|
if (c.indexOf(name)==0) return c.substring(name.length,c.length);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function receiveMessage(event)
|
function receiveMessage(event)
|
||||||
{
|
{
|
||||||
var origin = event.origin;
|
if (event.origin !== "ORIGIN") {
|
||||||
var data = event.data.split(' ');
|
console.log(event.origin + " does not match built origin");
|
||||||
if (data.length != 2) {
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
var data = JSON.parse(event.data);
|
||||||
|
data.loggedIn = false;
|
||||||
|
var cookie = getCookie('KEYCLOAK_SESSION');
|
||||||
|
if (cookie) {
|
||||||
|
data.loggedIn = true;
|
||||||
|
data.session = cookie;
|
||||||
}
|
}
|
||||||
|
|
||||||
var clientId = data[0];
|
event.source.postMessage(JSON.stringify(data),
|
||||||
var sessionState = data[1];
|
event.origin);
|
||||||
|
|
||||||
checkState(clientId, event.origin, sessionState, function(result) {
|
|
||||||
event.source.postMessage(result, origin);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("message", receiveMessage, false);
|
window.addEventListener("message", receiveMessage, false);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.5.9.Final</version>
|
<version>1.9.0.CR1</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@@ -36,32 +36,15 @@
|
|||||||
org.keycloak.adapters.osgi.*
|
org.keycloak.adapters.osgi.*
|
||||||
</keycloak.osgi.export>
|
</keycloak.osgi.export>
|
||||||
<keycloak.osgi.import>
|
<keycloak.osgi.import>
|
||||||
org.ops4j.pax.web.*;version="[3.0,5)",
|
org.ops4j.pax.web.*;version="[3.0,4)",
|
||||||
javax.servlet.*;version="[2.5,4)";resolution:=optional,
|
javax.servlet.*;version="[2.5,4)";resolution:=optional,
|
||||||
org.eclipse.jetty.*;version="[8.1,10)";resolution:=optional,
|
org.eclipse.jetty.*;version="[8.1,10)";resolution:=optional,
|
||||||
org.keycloak.*;version="${project.version}",
|
org.keycloak.*;version="${project.version}",
|
||||||
org.apache.cxf.transport.http;resolution:=optional;version="[3,4)",
|
|
||||||
org.apache.cxf.transport.servlet;resolution:=optional;version="[3,4)",
|
|
||||||
*;resolution:=optional
|
*;resolution:=optional
|
||||||
</keycloak.osgi.import>
|
</keycloak.osgi.import>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-core</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-adapter-spi</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.logging</groupId>
|
<groupId>org.jboss.logging</groupId>
|
||||||
<artifactId>jboss-logging</artifactId>
|
<artifactId>jboss-logging</artifactId>
|
||||||
@@ -82,23 +65,12 @@
|
|||||||
<artifactId>pax-web-runtime</artifactId>
|
<artifactId>pax-web-runtime</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.ops4j.pax.web</groupId>
|
|
||||||
<artifactId>pax-web-api</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</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>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.cxf</groupId>
|
|
||||||
<artifactId>cxf-rt-transports-http</artifactId>
|
|
||||||
<version>${cxf.version}</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
-83
@@ -1,83 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.adapters.osgi;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
import org.osgi.framework.BundleContext;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class BundleBasedKeycloakConfigResolver implements KeycloakConfigResolver {
|
|
||||||
|
|
||||||
private volatile KeycloakDeployment cachedDeployment;
|
|
||||||
|
|
||||||
private BundleContext bundleContext;
|
|
||||||
private String configLocation = "WEB-INF/keycloak.json";
|
|
||||||
|
|
||||||
public BundleContext getBundleContext() {
|
|
||||||
return bundleContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBundleContext(BundleContext bundleContext) {
|
|
||||||
this.bundleContext = bundleContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getConfigLocation() {
|
|
||||||
return configLocation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setConfigLocation(String configLocation) {
|
|
||||||
this.configLocation = configLocation;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeycloakDeployment resolve(HttpFacade.Request request) {
|
|
||||||
if (cachedDeployment != null) {
|
|
||||||
return cachedDeployment;
|
|
||||||
} else {
|
|
||||||
cachedDeployment = findDeployment(request);
|
|
||||||
return cachedDeployment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected KeycloakDeployment findDeployment(HttpFacade.Request request) {
|
|
||||||
if (bundleContext == null) {
|
|
||||||
throw new IllegalStateException("bundleContext must be set for BundleBasedKeycloakConfigResolver!");
|
|
||||||
}
|
|
||||||
|
|
||||||
URL url = bundleContext.getBundle().getResource(configLocation);
|
|
||||||
if (url == null) {
|
|
||||||
throw new IllegalStateException("Failed to find the file " + configLocation + " on classpath.");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
InputStream is = url.openStream();
|
|
||||||
return KeycloakDeploymentBuilder.build(is);
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
throw new IllegalStateException("Error reading file' " + configLocation + "' from bundle classpath.", ioe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-71
@@ -1,71 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.keycloak.adapters.osgi;
|
|
||||||
|
|
||||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
|
||||||
import org.keycloak.adapters.OIDCHttpFacade;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
public class PathBasedKeycloakConfigResolver implements KeycloakConfigResolver {
|
|
||||||
|
|
||||||
private final Map<String, KeycloakDeployment> cache = new ConcurrentHashMap<String, KeycloakDeployment>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeycloakDeployment resolve(OIDCHttpFacade.Request request) {
|
|
||||||
String path = request.getURI();
|
|
||||||
String[] urlTokens = path.split("/");
|
|
||||||
if (urlTokens.length < 4) {
|
|
||||||
throw new IllegalStateException("Not able to determine the web-context to load the correspondent keycloak.json file");
|
|
||||||
}
|
|
||||||
|
|
||||||
String webContext = urlTokens[3];
|
|
||||||
|
|
||||||
KeycloakDeployment deployment = cache.get(webContext);
|
|
||||||
if (null == deployment) {
|
|
||||||
// not found on the simple cache, try to load it from the file system
|
|
||||||
String keycloakConfig = (String) System.getProperties().get("keycloak.config");
|
|
||||||
if(keycloakConfig == null || "".equals(keycloakConfig.trim())){
|
|
||||||
String karafEtc = (String) System.getProperties().get("karaf.etc");
|
|
||||||
if(karafEtc == null || "".equals(karafEtc.trim())){
|
|
||||||
throw new IllegalStateException("Neither \"keycloak.config\" nor \"karaf.etc\" java properties are set. Please set one of them.");
|
|
||||||
}
|
|
||||||
keycloakConfig = karafEtc;
|
|
||||||
}
|
|
||||||
|
|
||||||
String absolutePath = keycloakConfig + File.separator + webContext + "-keycloak.json";
|
|
||||||
InputStream is = null;
|
|
||||||
try {
|
|
||||||
is = new FileInputStream(absolutePath);
|
|
||||||
} catch (FileNotFoundException e){
|
|
||||||
throw new IllegalStateException("Not able to find the file " + absolutePath);
|
|
||||||
}
|
|
||||||
deployment = KeycloakDeploymentBuilder.build(is);
|
|
||||||
cache.put(webContext, deployment);
|
|
||||||
}
|
|
||||||
|
|
||||||
return deployment;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
+5
-5
@@ -17,6 +17,11 @@
|
|||||||
|
|
||||||
package org.keycloak.adapters.osgi;
|
package org.keycloak.adapters.osgi;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.eclipse.jetty.security.ConstraintMapping;
|
import org.eclipse.jetty.security.ConstraintMapping;
|
||||||
import org.eclipse.jetty.util.security.Constraint;
|
import org.eclipse.jetty.util.security.Constraint;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
@@ -27,11 +32,6 @@ import org.osgi.service.http.HttpContext;
|
|||||||
import org.osgi.util.tracker.ServiceTracker;
|
import org.osgi.util.tracker.ServiceTracker;
|
||||||
import org.osgi.util.tracker.ServiceTrackerCustomizer;
|
import org.osgi.util.tracker.ServiceTrackerCustomizer;
|
||||||
|
|
||||||
import java.net.URL;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Integration with pax-web, which allows to inject custom jetty-web.xml configuration from current bundle classpath into {@link WebContainer}
|
* Integration with pax-web, which allows to inject custom jetty-web.xml configuration from current bundle classpath into {@link WebContainer}
|
||||||
* and allows to inject custom security constraint for securing resources by Keycloak.
|
* and allows to inject custom security constraint for securing resources by Keycloak.
|
||||||
|
|||||||
+33
-118
@@ -17,26 +17,20 @@
|
|||||||
|
|
||||||
package org.keycloak.adapters.osgi;
|
package org.keycloak.adapters.osgi;
|
||||||
|
|
||||||
import org.apache.cxf.transport.http.DestinationRegistry;
|
import java.util.Arrays;
|
||||||
import org.apache.cxf.transport.servlet.CXFNonSpringServlet;
|
import java.util.Hashtable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.servlet.Servlet;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.ops4j.pax.web.service.WebContainer;
|
import org.ops4j.pax.web.service.WebContainer;
|
||||||
import org.osgi.framework.BundleContext;
|
import org.osgi.framework.BundleContext;
|
||||||
import org.osgi.framework.ServiceReference;
|
import org.osgi.framework.ServiceReference;
|
||||||
import org.osgi.service.cm.Configuration;
|
|
||||||
import org.osgi.service.cm.ConfigurationAdmin;
|
|
||||||
import org.osgi.service.http.HttpContext;
|
import org.osgi.service.http.HttpContext;
|
||||||
import org.osgi.util.tracker.ServiceTracker;
|
import org.osgi.util.tracker.ServiceTracker;
|
||||||
import org.osgi.util.tracker.ServiceTrackerCustomizer;
|
import org.osgi.util.tracker.ServiceTrackerCustomizer;
|
||||||
|
|
||||||
import javax.servlet.Servlet;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Dictionary;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.Hashtable;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service, which allows to remove previously registered servlets in karaf/fuse environment. It assumes that particular servlet was previously
|
* Service, which allows to remove previously registered servlets in karaf/fuse environment. It assumes that particular servlet was previously
|
||||||
* registered as service in OSGI container under {@link javax.servlet.Servlet} interface.
|
* registered as service in OSGI container under {@link javax.servlet.Servlet} interface.
|
||||||
@@ -48,15 +42,13 @@ import java.util.Properties;
|
|||||||
*/
|
*/
|
||||||
public class ServletReregistrationService {
|
public class ServletReregistrationService {
|
||||||
|
|
||||||
private static final String CXF_SERVLET_PREFIX = "org.apache.cxf.servlet.";
|
|
||||||
protected static final Logger log = Logger.getLogger(ServletReregistrationService.class);
|
protected static final Logger log = Logger.getLogger(ServletReregistrationService.class);
|
||||||
|
|
||||||
private static final List<String> FILTERED_PROPERTIES = Arrays.asList("objectClass", "service.id");
|
private static final List<String> FILTERED_PROPERTIES = Arrays.asList("objectClass", "service.id");
|
||||||
|
|
||||||
private BundleContext bundleContext;
|
private BundleContext bundleContext;
|
||||||
private ServiceReference managedServiceReference;
|
private ServiceReference servletReference;
|
||||||
private ServiceTracker webContainerTracker;
|
private ServiceTracker webContainerTracker;
|
||||||
private String alias;
|
|
||||||
|
|
||||||
public BundleContext getBundleContext() {
|
public BundleContext getBundleContext() {
|
||||||
return bundleContext;
|
return bundleContext;
|
||||||
@@ -66,18 +58,24 @@ public class ServletReregistrationService {
|
|||||||
this.bundleContext = bundleContext;
|
this.bundleContext = bundleContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ServiceReference getServletReference() {
|
||||||
|
return servletReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServletReference(ServiceReference servletReference) {
|
||||||
|
this.servletReference = servletReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ServiceTracker getWebContainerTracker() {
|
||||||
|
return webContainerTracker;
|
||||||
|
}
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
if ( managedServiceReference == null) {
|
if (servletReference == null) {
|
||||||
return;
|
throw new IllegalStateException("No servlet reference provided");
|
||||||
}
|
|
||||||
|
|
||||||
Dictionary properties = obtainProperties();
|
|
||||||
alias = (String)getProp(properties, CXF_SERVLET_PREFIX + "context", "/cxf");
|
|
||||||
if(alias == null){
|
|
||||||
alias = "/cxf";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final Servlet servlet = (Servlet) bundleContext.getService(servletReference);
|
||||||
WebContainer externalWebContainer = findExternalWebContainer();
|
WebContainer externalWebContainer = findExternalWebContainer();
|
||||||
if (externalWebContainer == null) {
|
if (externalWebContainer == null) {
|
||||||
return;
|
return;
|
||||||
@@ -85,19 +83,18 @@ public class ServletReregistrationService {
|
|||||||
|
|
||||||
// Unregister servlet from external container now
|
// Unregister servlet from external container now
|
||||||
try {
|
try {
|
||||||
externalWebContainer.unregister(alias);
|
externalWebContainer.unregisterServlet(servlet);
|
||||||
log.debug("Original servlet with alias " + getAlias() + " unregistered successfully from external web container.");
|
log.debug("Original servlet with alias " + getAlias() + " unregistered successfully from external web container.");
|
||||||
} catch (IllegalStateException e) {
|
} catch (IllegalStateException e) {
|
||||||
log.warn("Can't unregister servlet due to: " + e.getMessage());
|
log.warn("Can't unregister servlet due to: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
final Dictionary finalProperties = properties;
|
|
||||||
ServiceTrackerCustomizer trackerCustomizer = new ServiceTrackerCustomizer() {
|
ServiceTrackerCustomizer trackerCustomizer = new ServiceTrackerCustomizer() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object addingService(ServiceReference webContainerServiceReference) {
|
public Object addingService(ServiceReference webContainerServiceReference) {
|
||||||
WebContainer ourWebContainer = (WebContainer) bundleContext.getService(webContainerServiceReference);
|
WebContainer ourWebContainer = (WebContainer) bundleContext.getService(webContainerServiceReference);
|
||||||
registerServlet(ourWebContainer, finalProperties);
|
registerServlet(ourWebContainer, servlet);
|
||||||
log.debugv("Servlet with alias " + getAlias() + " registered to secured web container");
|
log.debugv("Servlet with alias " + getAlias() + " registered to secured web container");
|
||||||
return ourWebContainer;
|
return ourWebContainer;
|
||||||
}
|
}
|
||||||
@@ -125,82 +122,28 @@ public class ServletReregistrationService {
|
|||||||
|
|
||||||
// Re-register servlet back to original context
|
// Re-register servlet back to original context
|
||||||
WebContainer externalWebContainer = findExternalWebContainer();
|
WebContainer externalWebContainer = findExternalWebContainer();
|
||||||
registerServlet(externalWebContainer, obtainProperties());
|
Servlet servlet = (Servlet) bundleContext.getService(servletReference);
|
||||||
|
registerServlet(externalWebContainer, servlet);
|
||||||
log.debug("Servlet with alias " + getAlias() + " registered back to external web container");
|
log.debug("Servlet with alias " + getAlias() + " registered back to external web container");
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getAlias() {
|
private String getAlias() {
|
||||||
return alias;
|
return (String) servletReference.getProperty("alias");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected void registerServlet(WebContainer webContainer, Servlet servlet) {
|
||||||
* Code comes from org.apache.cxf.transport.http.osgi.ServletExporter#updated(java.util.Dictionary)
|
|
||||||
* @param webContainer
|
|
||||||
* @param properties
|
|
||||||
*/
|
|
||||||
protected void registerServlet(WebContainer webContainer, Dictionary properties) {
|
|
||||||
HttpContext httpContext = webContainer.createDefaultHttpContext();
|
|
||||||
|
|
||||||
ServiceReference destinationServiceServiceReference = bundleContext.getServiceReference("org.apache.cxf.transport.http.DestinationRegistry");
|
|
||||||
DestinationRegistry destinationRegistry = (DestinationRegistry) bundleContext.getService(destinationServiceServiceReference);
|
|
||||||
|
|
||||||
Servlet servlet = new CXFNonSpringServlet(destinationRegistry, false);
|
|
||||||
try {
|
try {
|
||||||
if (properties == null) {
|
|
||||||
properties = new Properties();
|
|
||||||
}
|
|
||||||
Properties sprops = new Properties();
|
|
||||||
sprops.put("init-prefix",
|
|
||||||
getProp(properties, CXF_SERVLET_PREFIX + "init-prefix", ""));
|
|
||||||
sprops.put("servlet-name",
|
|
||||||
getProp(properties, CXF_SERVLET_PREFIX + "name", "cxf-osgi-transport-servlet"));
|
|
||||||
sprops.put("hide-service-list-page",
|
|
||||||
getProp(properties, CXF_SERVLET_PREFIX + "hide-service-list-page", "false"));
|
|
||||||
sprops.put("disable-address-updates",
|
|
||||||
getProp(properties, CXF_SERVLET_PREFIX + "disable-address-updates", "true"));
|
|
||||||
sprops.put("base-address",
|
|
||||||
getProp(properties, CXF_SERVLET_PREFIX + "base-address", ""));
|
|
||||||
sprops.put("service-list-path",
|
|
||||||
getProp(properties, CXF_SERVLET_PREFIX + "service-list-path", ""));
|
|
||||||
sprops.put("static-resources-list",
|
|
||||||
getProp(properties, CXF_SERVLET_PREFIX + "static-resources-list", ""));
|
|
||||||
sprops.put("redirects-list",
|
|
||||||
getProp(properties, CXF_SERVLET_PREFIX + "redirects-list", ""));
|
|
||||||
sprops.put("redirect-servlet-name",
|
|
||||||
getProp(properties, CXF_SERVLET_PREFIX + "redirect-servlet-name", ""));
|
|
||||||
sprops.put("redirect-servlet-path",
|
|
||||||
getProp(properties, CXF_SERVLET_PREFIX + "redirect-servlet-path", ""));
|
|
||||||
sprops.put("service-list-all-contexts",
|
|
||||||
getProp(properties, CXF_SERVLET_PREFIX + "service-list-all-contexts", ""));
|
|
||||||
sprops.put("service-list-page-authenticate",
|
|
||||||
getProp(properties, CXF_SERVLET_PREFIX + "service-list-page-authenticate", "false"));
|
|
||||||
sprops.put("service-list-page-authenticate-realm",
|
|
||||||
getProp(properties, CXF_SERVLET_PREFIX + "service-list-page-authenticate-realm", "karaf"));
|
|
||||||
sprops.put("use-x-forwarded-headers",
|
|
||||||
getProp(properties, CXF_SERVLET_PREFIX + "use-x-forwarded-headers", "false"));
|
|
||||||
|
|
||||||
// Accept extra properties by default, can be disabled if it is really needed
|
|
||||||
if (Boolean.valueOf(getProp(properties, CXF_SERVLET_PREFIX + "support.extra.properties", "true").toString())) {
|
|
||||||
Enumeration keys = properties.keys();
|
|
||||||
while (keys.hasMoreElements()) {
|
|
||||||
String nextKey = keys.nextElement().toString();
|
|
||||||
if (!nextKey.startsWith(CXF_SERVLET_PREFIX)) {
|
|
||||||
sprops.put(nextKey, properties.get(nextKey));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Hashtable<String, Object> servletInitParams = new Hashtable<String, Object>();
|
Hashtable<String, Object> servletInitParams = new Hashtable<String, Object>();
|
||||||
Enumeration keys = sprops.keys();
|
String[] propNames = servletReference.getPropertyKeys();
|
||||||
|
for (String propName : propNames) {
|
||||||
while(keys.hasMoreElements()){
|
|
||||||
String propName = (String) keys.nextElement();
|
|
||||||
if (!FILTERED_PROPERTIES.contains(propName)) {
|
if (!FILTERED_PROPERTIES.contains(propName)) {
|
||||||
servletInitParams.put(propName, sprops.getProperty(propName));
|
servletInitParams.put(propName, servletReference.getProperty(propName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to register servlet in given web container now
|
// Try to register servlet in given web container now
|
||||||
|
HttpContext httpContext = webContainer.createDefaultHttpContext();
|
||||||
|
String alias = (String) servletReference.getProperty("alias");
|
||||||
webContainer.registerServlet(alias, servlet, servletInitParams, httpContext);
|
webContainer.registerServlet(alias, servlet, servletInitParams, httpContext);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Can't register servlet in web container", e);
|
log.error("Can't register servlet in web container", e);
|
||||||
@@ -213,7 +156,7 @@ public class ServletReregistrationService {
|
|||||||
* @return web container or null
|
* @return web container or null
|
||||||
*/
|
*/
|
||||||
protected WebContainer findExternalWebContainer() {
|
protected WebContainer findExternalWebContainer() {
|
||||||
BundleContext servletBundleContext = managedServiceReference.getBundle().getBundleContext();
|
BundleContext servletBundleContext = servletReference.getBundle().getBundleContext();
|
||||||
ServiceReference webContainerReference = servletBundleContext.getServiceReference(WebContainer.class.getName());
|
ServiceReference webContainerReference = servletBundleContext.getServiceReference(WebContainer.class.getName());
|
||||||
if (webContainerReference == null) {
|
if (webContainerReference == null) {
|
||||||
log.warn("Not found webContainer reference for bundle " + servletBundleContext);
|
log.warn("Not found webContainer reference for bundle " + servletBundleContext);
|
||||||
@@ -223,32 +166,4 @@ public class ServletReregistrationService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Dictionary obtainProperties(){
|
|
||||||
Dictionary properties = null;
|
|
||||||
ServiceReference reference = bundleContext.getServiceReference(ConfigurationAdmin.class.getName());
|
|
||||||
ConfigurationAdmin admin = (ConfigurationAdmin) bundleContext.getService(reference);
|
|
||||||
try {
|
|
||||||
Configuration configuration = admin.getConfiguration("org.apache.cxf.osgi");
|
|
||||||
properties = configuration.getProperties();
|
|
||||||
} catch (Exception e){
|
|
||||||
log.warn("Unable to obtain cxf osgi configadmin reference.", e);
|
|
||||||
}
|
|
||||||
return properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object getProp(Dictionary properties, String key, Object defaultValue) {
|
|
||||||
Object value = null;
|
|
||||||
if(properties != null){
|
|
||||||
value = properties.get(key);
|
|
||||||
}
|
|
||||||
return value == null ? defaultValue : value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServiceReference getManagedServiceReference() {
|
|
||||||
return managedServiceReference;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setManagedServiceReference(ServiceReference managedServiceReference) {
|
|
||||||
this.managedServiceReference = managedServiceReference;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.5.9.Final</version>
|
<version>1.9.0.CR1</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>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.5.9.Final</version>
|
<version>1.9.0.CR1</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+11
-50
@@ -18,15 +18,15 @@
|
|||||||
package org.keycloak.adapters.servlet;
|
package org.keycloak.adapters.servlet;
|
||||||
|
|
||||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||||
|
import org.keycloak.adapters.spi.AuthChallenge;
|
||||||
|
import org.keycloak.adapters.spi.AuthOutcome;
|
||||||
import org.keycloak.adapters.AuthenticatedActionsHandler;
|
import org.keycloak.adapters.AuthenticatedActionsHandler;
|
||||||
|
import org.keycloak.adapters.spi.InMemorySessionIdMapper;
|
||||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
import org.keycloak.adapters.KeycloakConfigResolver;
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||||
import org.keycloak.adapters.NodesRegistrationManagement;
|
import org.keycloak.adapters.NodesRegistrationManagement;
|
||||||
import org.keycloak.adapters.PreAuthActionsHandler;
|
import org.keycloak.adapters.PreAuthActionsHandler;
|
||||||
import org.keycloak.adapters.spi.AuthChallenge;
|
|
||||||
import org.keycloak.adapters.spi.AuthOutcome;
|
|
||||||
import org.keycloak.adapters.spi.InMemorySessionIdMapper;
|
|
||||||
import org.keycloak.adapters.spi.SessionIdMapper;
|
import org.keycloak.adapters.spi.SessionIdMapper;
|
||||||
import org.keycloak.adapters.spi.UserSessionManagement;
|
import org.keycloak.adapters.spi.UserSessionManagement;
|
||||||
|
|
||||||
@@ -39,6 +39,7 @@ import javax.servlet.ServletResponse;
|
|||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletRequestWrapper;
|
import javax.servlet.http.HttpServletRequestWrapper;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -46,7 +47,6 @@ import java.io.InputStream;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
@@ -56,18 +56,10 @@ public class KeycloakOIDCFilter implements Filter {
|
|||||||
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;
|
|
||||||
|
|
||||||
private final static Logger log = Logger.getLogger(""+KeycloakOIDCFilter.class);
|
private final static Logger log = Logger.getLogger(""+KeycloakOIDCFilter.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(final FilterConfig filterConfig) throws ServletException {
|
public void init(final FilterConfig filterConfig) throws ServletException {
|
||||||
|
|
||||||
String skipPatternDefinition = filterConfig.getInitParameter("keycloak.config.skipPattern");
|
|
||||||
if (skipPatternDefinition != null) {
|
|
||||||
skipPattern = Pattern.compile(skipPatternDefinition, Pattern.DOTALL);
|
|
||||||
}
|
|
||||||
|
|
||||||
String configResolverClass = filterConfig.getInitParameter("keycloak.config.resolver");
|
String configResolverClass = filterConfig.getInitParameter("keycloak.config.resolver");
|
||||||
if (configResolverClass != null) {
|
if (configResolverClass != null) {
|
||||||
try {
|
try {
|
||||||
@@ -93,7 +85,13 @@ public class KeycloakOIDCFilter implements Filter {
|
|||||||
if (pathParam != null) path = pathParam;
|
if (pathParam != null) path = pathParam;
|
||||||
is = filterConfig.getServletContext().getResourceAsStream(path);
|
is = filterConfig.getServletContext().getResourceAsStream(path);
|
||||||
}
|
}
|
||||||
KeycloakDeployment kd = createKeycloakDeploymentFrom(is);
|
KeycloakDeployment kd;
|
||||||
|
if (is == null) {
|
||||||
|
log.fine("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
|
||||||
|
kd = new KeycloakDeployment();
|
||||||
|
} else {
|
||||||
|
kd = KeycloakDeploymentBuilder.build(is);
|
||||||
|
}
|
||||||
deploymentContext = new AdapterDeploymentContext(kd);
|
deploymentContext = new AdapterDeploymentContext(kd);
|
||||||
log.fine("Keycloak is using a per-deployment configuration.");
|
log.fine("Keycloak is using a per-deployment configuration.");
|
||||||
}
|
}
|
||||||
@@ -101,30 +99,13 @@ public class KeycloakOIDCFilter implements Filter {
|
|||||||
nodesRegistrationManagement = new NodesRegistrationManagement();
|
nodesRegistrationManagement = new NodesRegistrationManagement();
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeycloakDeployment createKeycloakDeploymentFrom(InputStream is) {
|
|
||||||
|
|
||||||
if (is == null) {
|
|
||||||
log.fine("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
|
|
||||||
return new KeycloakDeployment();
|
|
||||||
}
|
|
||||||
|
|
||||||
return KeycloakDeploymentBuilder.build(is);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@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());
|
//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;
|
||||||
|
|
||||||
if (shouldSkip(request)) {
|
|
||||||
chain.doFilter(req, res);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
OIDCServletHttpFacade facade = new OIDCServletHttpFacade(request, response);
|
OIDCServletHttpFacade facade = new OIDCServletHttpFacade(request, response);
|
||||||
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
|
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
|
||||||
if (deployment == null || !deployment.isConfigured()) {
|
if (deployment == null || !deployment.isConfigured()) {
|
||||||
@@ -190,26 +171,6 @@ public class KeycloakOIDCFilter implements Filter {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Decides whether this {@link Filter} should skip the given {@link HttpServletRequest} based on the configured {@link KeycloakOIDCFilter#skipPattern}.
|
|
||||||
* Patterns are matched against the {@link HttpServletRequest#getRequestURI() requestURI} of a request without the context-path.
|
|
||||||
* A request for {@code /myapp/index.html} would be tested with {@code /index.html} against the skip pattern.
|
|
||||||
* Skipped requests will not be processed further by {@link KeycloakOIDCFilter} and immediately delegated to the {@link FilterChain}.
|
|
||||||
*
|
|
||||||
* @param request the request to check
|
|
||||||
* @return {@code true} if the request should not be handled,
|
|
||||||
* {@code false} otherwise.
|
|
||||||
*/
|
|
||||||
private boolean shouldSkip(HttpServletRequest request) {
|
|
||||||
|
|
||||||
if (skipPattern == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
String requestPath = request.getRequestURI().substring(request.getContextPath().length());
|
|
||||||
return skipPattern.matcher(requestPath).matches();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void destroy() {
|
public void destroy() {
|
||||||
|
|
||||||
|
|||||||
+5
-13
@@ -19,12 +19,12 @@ package org.keycloak.adapters.servlet;
|
|||||||
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
import org.keycloak.KeycloakSecurityContext;
|
||||||
import org.keycloak.adapters.AdapterTokenStore;
|
import org.keycloak.adapters.AdapterTokenStore;
|
||||||
|
import org.keycloak.adapters.spi.HttpFacade;
|
||||||
|
import org.keycloak.adapters.spi.KeycloakAccount;
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
import org.keycloak.adapters.OidcKeycloakAccount;
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||||
import org.keycloak.adapters.RequestAuthenticator;
|
import org.keycloak.adapters.RequestAuthenticator;
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
import org.keycloak.adapters.spi.KeycloakAccount;
|
|
||||||
import org.keycloak.adapters.spi.SessionIdMapper;
|
import org.keycloak.adapters.spi.SessionIdMapper;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
@@ -51,16 +51,10 @@ public class OIDCFilterSessionStore extends FilterSessionStore implements Adapte
|
|||||||
}
|
}
|
||||||
|
|
||||||
public HttpServletRequestWrapper buildWrapper() {
|
public HttpServletRequestWrapper buildWrapper() {
|
||||||
HttpSession session = request.getSession(false);
|
HttpSession session = request.getSession();
|
||||||
KeycloakAccount account = null;
|
KeycloakAccount account = (KeycloakAccount)session.getAttribute(KeycloakAccount.class.getName());
|
||||||
if (session != null) {
|
|
||||||
account = (KeycloakAccount) session.getAttribute(KeycloakAccount.class.getName());
|
|
||||||
if (account == null) {
|
|
||||||
account = (KeycloakAccount) request.getAttribute(KeycloakAccount.class.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (account == null) {
|
if (account == null) {
|
||||||
account = (KeycloakAccount) request.getAttribute(KeycloakAccount.class.getName());
|
account = (KeycloakAccount)request.getAttribute(KeycloakAccount.class.getName());
|
||||||
}
|
}
|
||||||
return buildWrapper(session, account);
|
return buildWrapper(session, account);
|
||||||
}
|
}
|
||||||
@@ -95,7 +89,6 @@ public class OIDCFilterSessionStore extends FilterSessionStore implements Adapte
|
|||||||
|
|
||||||
protected void cleanSession(HttpSession session) {
|
protected void cleanSession(HttpSession session) {
|
||||||
session.removeAttribute(KeycloakAccount.class.getName());
|
session.removeAttribute(KeycloakAccount.class.getName());
|
||||||
session.removeAttribute(KeycloakSecurityContext.class.getName());
|
|
||||||
clearSavedRequest(session);
|
clearSavedRequest(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +160,6 @@ public class OIDCFilterSessionStore extends FilterSessionStore implements Adapte
|
|||||||
SerializableKeycloakAccount sAccount = new SerializableKeycloakAccount(roles, account.getPrincipal(), securityContext);
|
SerializableKeycloakAccount sAccount = new SerializableKeycloakAccount(roles, account.getPrincipal(), securityContext);
|
||||||
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());
|
|
||||||
if (idMapper != null) idMapper.map(account.getKeycloakSecurityContext().getToken().getClientSession(), 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>2.5.9.Final</version>
|
<version>1.9.0.CR1</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+2
-2
@@ -17,12 +17,12 @@
|
|||||||
|
|
||||||
package org.keycloak.servlet;
|
package org.keycloak.servlet;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.keycloak.AbstractOAuthClient;
|
import org.keycloak.AbstractOAuthClient;
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
import org.keycloak.common.enums.RelativeUrlsUsed;
|
import org.keycloak.common.enums.RelativeUrlsUsed;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
|
|||||||
Loaded 100 of 5515 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user