Refactor PolicyEnforcer tests to remove dependency on keycloak-adapter-core and remove keycloak-adapter-core
Closes #29189 Closes #28791 Signed-off-by: Douglas Palmer <dpalmer@redhat.com>
This commit is contained in:
parent
8d76ce3f54
commit
b9c04bb8bc
56 changed files with 30 additions and 2424 deletions
|
@ -1,162 +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>999.0.0-SNAPSHOT</version>
|
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
|
||||||
<name>Keycloak Adapter Core</name>
|
|
||||||
<description/>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<keycloak.osgi.export>
|
|
||||||
org.keycloak.adapters.*
|
|
||||||
</keycloak.osgi.export>
|
|
||||||
<keycloak.osgi.import>
|
|
||||||
org.keycloak.*;version="${project.version}",
|
|
||||||
org.apache.http.auth.*;version=${apache.httpcomponents.fuse.version},
|
|
||||||
org.apache.http.client.*;version=${apache.httpcomponents.fuse.version},
|
|
||||||
org.apache.http.conn.*;version=${apache.httpcomponents.fuse.version},
|
|
||||||
org.apache.http.cookie.*;version=${apache.httpcomponents.fuse.version},
|
|
||||||
org.apache.http.impl.auth.*;version=${apache.httpcomponents.fuse.version},
|
|
||||||
org.apache.http.impl.client.*;version=${apache.httpcomponents.fuse.version},
|
|
||||||
org.apache.http.impl.conn.*;version=${apache.httpcomponents.fuse.version},
|
|
||||||
org.apache.http.impl.cookie.*;version=${apache.httpcomponents.fuse.version},
|
|
||||||
org.apache.http.impl.execchain.*;version=${apache.httpcomponents.fuse.version},
|
|
||||||
org.apache.http.*;version=${apache.httpcomponents.httpcore.fuse.version},
|
|
||||||
*;resolution:=optional
|
|
||||||
</keycloak.osgi.import>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.bouncycastle</groupId>
|
|
||||||
<artifactId>bcprov-jdk18on</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.logging</groupId>
|
|
||||||
<artifactId>jboss-logging</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.logging</groupId>
|
|
||||||
<artifactId>commons-logging-jboss-logging</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-adapter-spi</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-core</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>${keycloak.crypto.artifactId}</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-authz-client</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-policy-enforcer</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
|
||||||
<artifactId>jackson-core</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
|
||||||
<artifactId>jackson-databind</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
|
||||||
<artifactId>jackson-annotations</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>junit</groupId>
|
|
||||||
<artifactId>junit</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.httpcomponents</groupId>
|
|
||||||
<artifactId>httpclient</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<!-- 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>
|
|
||||||
<Require-Capability>
|
|
||||||
osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)", osgi.serviceloader; filter:="(osgi.serviceloader=org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory)";cardinality:=multiple, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)"
|
|
||||||
</Require-Capability>
|
|
||||||
<Provide-Capability>
|
|
||||||
osgi.serviceloader; osgi.serviceloader=org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory
|
|
||||||
</Provide-Capability>
|
|
||||||
</instructions>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
|
|
||||||
</project>
|
|
|
@ -1,61 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.adapters;
|
|
||||||
|
|
||||||
import org.keycloak.adapters.spi.AdapterSessionStore;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstraction for storing token info on adapter side. Intended to be per-request object
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public interface AdapterTokenStore extends AdapterSessionStore {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Impl can validate if current token exists and perform refreshing if it exists and is expired
|
|
||||||
*/
|
|
||||||
void checkCurrentToken();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if we are logged already (we have already valid and successfully refreshed accessToken). Establish security context if yes
|
|
||||||
*
|
|
||||||
* @param authenticator used for actual request authentication
|
|
||||||
* @return true if we are logged-in already
|
|
||||||
*/
|
|
||||||
boolean isCached(RequestAuthenticator authenticator);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finish successful OAuth2 login and store validated account
|
|
||||||
*
|
|
||||||
* @param account
|
|
||||||
*/
|
|
||||||
void saveAccountInfo(OidcKeycloakAccount account);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle logout on store side and possibly propagate logout call to Keycloak
|
|
||||||
*/
|
|
||||||
void logout();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback invoked after successful token refresh
|
|
||||||
*
|
|
||||||
* @param securityContext context where refresh was performed
|
|
||||||
*/
|
|
||||||
void refreshCallback(RefreshableKeycloakSecurityContext securityContext);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,124 +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.NameValuePair;
|
|
||||||
import org.apache.http.client.HttpClient;
|
|
||||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
|
||||||
import org.apache.http.client.methods.HttpPost;
|
|
||||||
import org.apache.http.message.BasicNameValuePair;
|
|
||||||
import org.apache.http.util.EntityUtils;
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.keycloak.OAuth2Constants;
|
|
||||||
import org.keycloak.adapters.spi.AuthOutcome;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
import org.keycloak.common.util.Base64;
|
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
|
||||||
import org.keycloak.util.JsonSerialization;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Basic auth request authenticator.
|
|
||||||
*/
|
|
||||||
public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticator {
|
|
||||||
protected Logger log = Logger.getLogger(BasicAuthRequestAuthenticator.class);
|
|
||||||
|
|
||||||
public BasicAuthRequestAuthenticator(KeycloakDeployment deployment) {
|
|
||||||
super(deployment);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthOutcome authenticate(HttpFacade exchange) {
|
|
||||||
List<String> authHeaders = exchange.getRequest().getHeaders("Authorization");
|
|
||||||
if (authHeaders == null || authHeaders.isEmpty()) {
|
|
||||||
log.debug("Authorization header not present");
|
|
||||||
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_AUTHORIZATION_HEADER, null, null);
|
|
||||||
return AuthOutcome.NOT_ATTEMPTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenString = null;
|
|
||||||
for (String authHeader : authHeaders) {
|
|
||||||
String[] split = authHeader.trim().split("\\s+");
|
|
||||||
if (split.length != 2) continue;
|
|
||||||
if (!split[0].equalsIgnoreCase("Basic")) continue;
|
|
||||||
tokenString = split[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tokenString == null) {
|
|
||||||
log.debug("Token is not present in Authorization header");
|
|
||||||
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, null, null);
|
|
||||||
return AuthOutcome.NOT_ATTEMPTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
AccessTokenResponse atr=null;
|
|
||||||
try {
|
|
||||||
String userpw=new String(Base64.decode(tokenString));
|
|
||||||
int seperatorIndex = userpw.indexOf(":");
|
|
||||||
String user = userpw.substring(0, seperatorIndex);
|
|
||||||
String pw = userpw.substring(seperatorIndex + 1);
|
|
||||||
atr = getToken(user, pw);
|
|
||||||
tokenString = atr.getToken();
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.debug("Failed to obtain token", e);
|
|
||||||
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "no_token", e.getMessage());
|
|
||||||
return AuthOutcome.FAILED;
|
|
||||||
}
|
|
||||||
|
|
||||||
return authenticateToken(exchange, atr.getToken());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected AccessTokenResponse getToken(String username, String password) throws Exception {
|
|
||||||
AccessTokenResponse tokenResponse=null;
|
|
||||||
HttpClient client = deployment.getClient();
|
|
||||||
|
|
||||||
HttpPost post = new HttpPost(deployment.getTokenUrl());
|
|
||||||
java.util.List <NameValuePair> formparams = new java.util.ArrayList <NameValuePair>();
|
|
||||||
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD));
|
|
||||||
formparams.add(new BasicNameValuePair("username", username));
|
|
||||||
formparams.add(new BasicNameValuePair("password", password));
|
|
||||||
|
|
||||||
AdapterUtils.setClientCredentials(deployment, post, formparams);
|
|
||||||
|
|
||||||
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
|
|
||||||
post.setEntity(form);
|
|
||||||
|
|
||||||
HttpResponse response = client.execute(post);
|
|
||||||
int status = response.getStatusLine().getStatusCode();
|
|
||||||
HttpEntity entity = response.getEntity();
|
|
||||||
if (status != 200) {
|
|
||||||
EntityUtils.consumeQuietly(entity);
|
|
||||||
throw new java.io.IOException("Bad status: " + status);
|
|
||||||
}
|
|
||||||
if (entity == null) {
|
|
||||||
throw new java.io.IOException("No Entity");
|
|
||||||
}
|
|
||||||
java.io.InputStream is = entity.getContent();
|
|
||||||
try {
|
|
||||||
tokenResponse = JsonSerialization.readValue(is, AccessTokenResponse.class);
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
is.close();
|
|
||||||
} catch (java.io.IOException ignored) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
return (tokenResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,203 +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.pep.HttpAuthzRequest;
|
|
||||||
import org.keycloak.adapters.pep.HttpAuthzResponse;
|
|
||||||
import org.keycloak.adapters.rotation.AdapterTokenVerifier;
|
|
||||||
import org.keycloak.adapters.spi.AuthChallenge;
|
|
||||||
import org.keycloak.adapters.spi.AuthOutcome;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
import org.keycloak.common.VerificationException;
|
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
|
||||||
import org.keycloak.jose.jws.JWSInputException;
|
|
||||||
import org.keycloak.representations.AccessToken;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import javax.security.cert.X509Certificate;
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class BearerTokenRequestAuthenticator {
|
|
||||||
protected Logger log = Logger.getLogger(BearerTokenRequestAuthenticator.class);
|
|
||||||
protected String tokenString;
|
|
||||||
protected AccessToken token;
|
|
||||||
protected String surrogate;
|
|
||||||
protected AuthChallenge challenge;
|
|
||||||
protected KeycloakDeployment deployment;
|
|
||||||
|
|
||||||
public BearerTokenRequestAuthenticator(KeycloakDeployment deployment) {
|
|
||||||
this.deployment = deployment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthChallenge getChallenge() {
|
|
||||||
return challenge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTokenString() {
|
|
||||||
return tokenString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AccessToken getToken() {
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSurrogate() {
|
|
||||||
return surrogate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthOutcome authenticate(HttpFacade exchange) {
|
|
||||||
List<String> authHeaders = exchange.getRequest().getHeaders("Authorization");
|
|
||||||
if (authHeaders == null || authHeaders.isEmpty()) {
|
|
||||||
log.debug("Authorization header not present");
|
|
||||||
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_BEARER_TOKEN, null, null);
|
|
||||||
return AuthOutcome.NOT_ATTEMPTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenString = null;
|
|
||||||
for (String authHeader : authHeaders) {
|
|
||||||
String[] split = authHeader.trim().split("\\s+");
|
|
||||||
if (split.length != 2) continue;
|
|
||||||
if (split[0].equalsIgnoreCase("Bearer")) {
|
|
||||||
tokenString = split[1];
|
|
||||||
|
|
||||||
log.debugf("Found [%d] values in authorization header, selecting the first value for Bearer.", (Integer) authHeaders.size());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tokenString == null) {
|
|
||||||
log.debug("Token is not present in Authorization header");
|
|
||||||
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_BEARER_TOKEN, null, null);
|
|
||||||
return AuthOutcome.NOT_ATTEMPTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (authenticateToken(exchange, tokenString));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected AuthOutcome authenticateToken(HttpFacade exchange, String tokenString) {
|
|
||||||
log.debug("Verifying access_token");
|
|
||||||
if (log.isTraceEnabled()) {
|
|
||||||
try {
|
|
||||||
JWSInput jwsInput = new JWSInput(tokenString);
|
|
||||||
String wireString = jwsInput.getWireString();
|
|
||||||
log.tracef("\taccess_token: %s", wireString.substring(0, wireString.lastIndexOf(".")) + ".signature");
|
|
||||||
} catch (JWSInputException e) {
|
|
||||||
log.errorf(e, "Failed to parse access_token: %s", tokenString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
token = AdapterTokenVerifier.verifyToken(tokenString, deployment);
|
|
||||||
} catch (VerificationException e) {
|
|
||||||
log.debugf("Failed to verify token: %s", e.getMessage());
|
|
||||||
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "invalid_token", e.getMessage());
|
|
||||||
return AuthOutcome.FAILED;
|
|
||||||
}
|
|
||||||
if (token.getIat() < deployment.getNotBefore()) {
|
|
||||||
log.debug("Stale token");
|
|
||||||
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.STALE_TOKEN, "invalid_token", "Stale token");
|
|
||||||
return AuthOutcome.FAILED;
|
|
||||||
}
|
|
||||||
boolean verifyCaller = false;
|
|
||||||
if (deployment.isUseResourceRoleMappings()) {
|
|
||||||
verifyCaller = token.isVerifyCaller(deployment.getResourceName());
|
|
||||||
} else {
|
|
||||||
verifyCaller = token.isVerifyCaller();
|
|
||||||
}
|
|
||||||
surrogate = null;
|
|
||||||
if (verifyCaller) {
|
|
||||||
if (token.getTrustedCertificates() == null || token.getTrustedCertificates().isEmpty()) {
|
|
||||||
log.warn("No trusted certificates in token");
|
|
||||||
challenge = clientCertChallenge();
|
|
||||||
return AuthOutcome.FAILED;
|
|
||||||
}
|
|
||||||
|
|
||||||
// for now, we just make sure Undertow did two-way SSL
|
|
||||||
// assume JBoss Web verifies the client cert
|
|
||||||
X509Certificate[] chain = new X509Certificate[0];
|
|
||||||
try {
|
|
||||||
chain = exchange.getCertificateChain();
|
|
||||||
} catch (Exception ignore) {
|
|
||||||
|
|
||||||
}
|
|
||||||
if (chain == null || chain.length == 0) {
|
|
||||||
log.warn("No certificates provided by undertow to verify the caller");
|
|
||||||
challenge = clientCertChallenge();
|
|
||||||
return AuthOutcome.FAILED;
|
|
||||||
}
|
|
||||||
surrogate = chain[0].getSubjectDN().getName();
|
|
||||||
}
|
|
||||||
log.debug("successful authorized");
|
|
||||||
return AuthOutcome.AUTHENTICATED;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected AuthChallenge clientCertChallenge() {
|
|
||||||
return new AuthChallenge() {
|
|
||||||
@Override
|
|
||||||
public int getResponseCode() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean challenge(HttpFacade exchange) {
|
|
||||||
// do the same thing as client cert auth
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected AuthChallenge challengeResponse(HttpFacade facade, final OIDCAuthenticationError.Reason reason, final String error, final String description) {
|
|
||||||
StringBuilder header = new StringBuilder("Bearer realm=\"");
|
|
||||||
header.append(deployment.getRealm()).append("\"");
|
|
||||||
if (error != null) {
|
|
||||||
header.append(", error=\"").append(error).append("\"");
|
|
||||||
}
|
|
||||||
if (description != null) {
|
|
||||||
header.append(", error_description=\"").append(description).append("\"");
|
|
||||||
}
|
|
||||||
final String challenge = header.toString();
|
|
||||||
return new AuthChallenge() {
|
|
||||||
@Override
|
|
||||||
public int getResponseCode() {
|
|
||||||
return 401;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean challenge(HttpFacade facade) {
|
|
||||||
OIDCHttpFacade oidcFacade = (OIDCHttpFacade) facade;
|
|
||||||
if (deployment.getPolicyEnforcer() != null) {
|
|
||||||
deployment.getPolicyEnforcer().enforce(new HttpAuthzRequest(oidcFacade), new HttpAuthzResponse(oidcFacade));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
OIDCAuthenticationError error = new OIDCAuthenticationError(reason, description);
|
|
||||||
facade.getRequest().setError(error);
|
|
||||||
facade.getResponse().addHeader("WWW-Authenticate", challenge);
|
|
||||||
if(deployment.isDelegateBearerErrorResponseSending()){
|
|
||||||
facade.getResponse().setStatus(401);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
facade.getResponse().sendError(401);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,128 +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.KeycloakPrincipal;
|
|
||||||
import org.keycloak.TokenVerifier;
|
|
||||||
import org.keycloak.adapters.rotation.AdapterTokenVerifier;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
import org.keycloak.common.VerificationException;
|
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
|
||||||
import org.keycloak.constants.AdapterConstants;
|
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
|
||||||
import org.keycloak.jose.jws.JWSInputException;
|
|
||||||
import org.keycloak.representations.AccessToken;
|
|
||||||
import org.keycloak.representations.IDToken;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class CookieTokenStore {
|
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(CookieTokenStore.class);
|
|
||||||
private static final String DELIM = "___";
|
|
||||||
|
|
||||||
public static void setTokenCookie(KeycloakDeployment deployment, HttpFacade facade, RefreshableKeycloakSecurityContext session) {
|
|
||||||
log.debugf("Set new %s cookie now", AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE);
|
|
||||||
String accessToken = session.getTokenString();
|
|
||||||
String idToken = session.getIdTokenString();
|
|
||||||
String refreshToken = session.getRefreshToken();
|
|
||||||
String cookie = new StringBuilder(accessToken).append(DELIM)
|
|
||||||
.append(idToken).append(DELIM)
|
|
||||||
.append(refreshToken).toString();
|
|
||||||
|
|
||||||
String cookiePath = getCookiePath(deployment, facade);
|
|
||||||
facade.getResponse().setCookie(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE, cookie, cookiePath, null, -1, deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr()), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static KeycloakPrincipal<RefreshableKeycloakSecurityContext> getPrincipalFromCookie(KeycloakDeployment deployment, HttpFacade facade, AdapterTokenStore tokenStore) {
|
|
||||||
OIDCHttpFacade.Cookie cookie = facade.getRequest().getCookie(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE);
|
|
||||||
if (cookie == null) {
|
|
||||||
log.debug("Not found adapter state cookie in current request");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String cookieVal = cookie.getValue();
|
|
||||||
|
|
||||||
String[] tokens = cookieVal.split(DELIM);
|
|
||||||
if (tokens.length != 3) {
|
|
||||||
log.warnf("Invalid format of %s cookie. Count of tokens: %s, expected 3", AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE, tokens.length);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String accessTokenString = tokens[0];
|
|
||||||
String idTokenString = tokens[1];
|
|
||||||
String refreshTokenString = tokens[2];
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Skip check if token is active now. It's supposed to be done later by the caller
|
|
||||||
TokenVerifier<AccessToken> tokenVerifier = AdapterTokenVerifier.createVerifier(accessTokenString, deployment, true, AccessToken.class)
|
|
||||||
.checkActive(false)
|
|
||||||
.verify();
|
|
||||||
AccessToken accessToken = tokenVerifier.getToken();
|
|
||||||
|
|
||||||
IDToken idToken;
|
|
||||||
if (idTokenString != null && idTokenString.length() > 0) {
|
|
||||||
try {
|
|
||||||
JWSInput input = new JWSInput(idTokenString);
|
|
||||||
idToken = input.readJsonContent(IDToken.class);
|
|
||||||
} catch (JWSInputException e) {
|
|
||||||
throw new VerificationException(e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
idToken = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug("Token Verification succeeded!");
|
|
||||||
RefreshableKeycloakSecurityContext secContext = new RefreshableKeycloakSecurityContext(deployment, tokenStore, accessTokenString, accessToken, idTokenString, idToken, refreshTokenString);
|
|
||||||
return new KeycloakPrincipal<>(AdapterUtils.getPrincipalName(deployment, accessToken), secContext);
|
|
||||||
} catch (VerificationException ve) {
|
|
||||||
log.warn("Failed verify token", ve);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void removeCookie(KeycloakDeployment deployment, HttpFacade facade) {
|
|
||||||
String cookiePath = getCookiePath(deployment, facade);
|
|
||||||
facade.getResponse().resetCookie(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE, cookiePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
static String getCookiePath(KeycloakDeployment deployment, HttpFacade facade) {
|
|
||||||
String path = deployment.getAdapterStateCookiePath() == null ? "" : deployment.getAdapterStateCookiePath().trim();
|
|
||||||
if (path.startsWith("/")) {
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
String contextPath = getContextPath(facade);
|
|
||||||
StringBuilder cookiePath = new StringBuilder(contextPath);
|
|
||||||
if (!contextPath.endsWith("/") && !path.isEmpty()) {
|
|
||||||
cookiePath.append("/");
|
|
||||||
}
|
|
||||||
return cookiePath.append(path).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
static String getContextPath(HttpFacade facade) {
|
|
||||||
String uri = facade.getRequest().getURI();
|
|
||||||
String path = KeycloakUriBuilder.fromUri(uri).getPath();
|
|
||||||
if (path == null || path.isEmpty()) {
|
|
||||||
return "/";
|
|
||||||
}
|
|
||||||
int index = path.indexOf("/", 1);
|
|
||||||
return index == -1 ? path : path.substring(0, index);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.keycloak.common.util.HostUtils;
|
|
||||||
import org.keycloak.common.util.Time;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class NodesRegistrationManagement {
|
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(NodesRegistrationManagement.class);
|
|
||||||
|
|
||||||
private final Map<String, NodeRegistrationContext> nodeRegistrations = new ConcurrentHashMap<String, NodeRegistrationContext>();
|
|
||||||
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
|
||||||
|
|
||||||
// Sending registration event during first request to application or if re-registration is needed
|
|
||||||
public void tryRegister(final KeycloakDeployment resolvedDeployment) {
|
|
||||||
if (resolvedDeployment.isRegisterNodeAtStartup()) {
|
|
||||||
final String registrationUri = resolvedDeployment.getRegisterNodeUrl();
|
|
||||||
if (needRefreshRegistration(registrationUri, resolvedDeployment)) {
|
|
||||||
Runnable runnable = new Runnable() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
// Need to check it again in case that executor triggered by other thread already finished computation in the meantime
|
|
||||||
if (needRefreshRegistration(registrationUri, resolvedDeployment)) {
|
|
||||||
sendRegistrationEvent(resolvedDeployment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
executor.execute(runnable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean needRefreshRegistration(String registrationUri, KeycloakDeployment resolvedDeployment) {
|
|
||||||
NodeRegistrationContext currentRegistration = nodeRegistrations.get(registrationUri);
|
|
||||||
/// We don't yet have any registration for this node
|
|
||||||
if (currentRegistration == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return currentRegistration.lastRegistrationTime + resolvedDeployment.getRegisterNodePeriod() < Time.currentTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called during undeployment or server stop. De-register from all previously registered deployments
|
|
||||||
*/
|
|
||||||
public void stop() {
|
|
||||||
executor.shutdownNow();
|
|
||||||
|
|
||||||
Collection<NodeRegistrationContext> allRegistrations = nodeRegistrations.values();
|
|
||||||
for (NodeRegistrationContext registration : allRegistrations) {
|
|
||||||
sendUnregistrationEvent(registration.resolvedDeployment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void sendRegistrationEvent(KeycloakDeployment deployment) {
|
|
||||||
// This method is invoked from single-thread executor, so no synchronization is needed
|
|
||||||
// However, it could happen that the same deployment was submitted more than once to that executor
|
|
||||||
// Hence we need to recheck that the registration is really needed
|
|
||||||
final String registrationUri = deployment.getRegisterNodeUrl();
|
|
||||||
if (! needRefreshRegistration(registrationUri, deployment)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Thread.currentThread().isInterrupted()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug("Sending registration event right now");
|
|
||||||
|
|
||||||
String host = HostUtils.getHostName();
|
|
||||||
try {
|
|
||||||
ServerRequest.invokeRegisterNode(deployment, host);
|
|
||||||
NodeRegistrationContext regContext = new NodeRegistrationContext(Time.currentTime(), deployment);
|
|
||||||
nodeRegistrations.put(deployment.getRegisterNodeUrl(), regContext);
|
|
||||||
log.debugf("Node '%s' successfully registered in Keycloak", host);
|
|
||||||
} catch (ServerRequest.HttpFailure failure) {
|
|
||||||
log.error("failed to register node to keycloak");
|
|
||||||
log.error("status from server: " + failure.getStatus());
|
|
||||||
if (failure.getError() != null) {
|
|
||||||
log.error(" " + failure.getError());
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("failed to register node to keycloak", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean sendUnregistrationEvent(KeycloakDeployment deployment) {
|
|
||||||
log.debug("Sending Unregistration event right now");
|
|
||||||
|
|
||||||
String host = HostUtils.getHostName();
|
|
||||||
try {
|
|
||||||
ServerRequest.invokeUnregisterNode(deployment, host);
|
|
||||||
log.debugf("Node '%s' successfully unregistered from Keycloak", host);
|
|
||||||
return true;
|
|
||||||
} catch (ServerRequest.HttpFailure failure) {
|
|
||||||
log.error("failed to unregister node from keycloak");
|
|
||||||
log.error("status from server: " + failure.getStatus());
|
|
||||||
if (failure.getError() != null) {
|
|
||||||
log.error(" " + failure.getError());
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("failed to unregister node from keycloak", e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class NodeRegistrationContext {
|
|
||||||
|
|
||||||
private final Integer lastRegistrationTime;
|
|
||||||
// deployment instance used for registration request
|
|
||||||
private final KeycloakDeployment resolvedDeployment;
|
|
||||||
|
|
||||||
public NodeRegistrationContext(Integer lastRegTime, KeycloakDeployment deployment) {
|
|
||||||
this.lastRegistrationTime = lastRegTime;
|
|
||||||
this.resolvedDeployment = deployment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,419 +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.OAuth2Constants;
|
|
||||||
import org.keycloak.adapters.rotation.AdapterTokenVerifier;
|
|
||||||
import org.keycloak.adapters.spi.AdapterSessionStore;
|
|
||||||
import org.keycloak.adapters.spi.AuthChallenge;
|
|
||||||
import org.keycloak.adapters.spi.AuthOutcome;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
import org.keycloak.common.VerificationException;
|
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
|
||||||
import org.keycloak.common.util.UriUtils;
|
|
||||||
import org.keycloak.constants.AdapterConstants;
|
|
||||||
import org.keycloak.enums.TokenStore;
|
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
|
||||||
import org.keycloak.jose.jws.JWSInputException;
|
|
||||||
import org.keycloak.representations.AccessToken;
|
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
|
||||||
import org.keycloak.representations.IDToken;
|
|
||||||
import org.keycloak.util.TokenUtil;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class OAuthRequestAuthenticator {
|
|
||||||
private static final Logger log = Logger.getLogger(OAuthRequestAuthenticator.class);
|
|
||||||
protected KeycloakDeployment deployment;
|
|
||||||
protected RequestAuthenticator reqAuthenticator;
|
|
||||||
protected int sslRedirectPort;
|
|
||||||
protected AdapterSessionStore tokenStore;
|
|
||||||
protected String tokenString;
|
|
||||||
protected String idTokenString;
|
|
||||||
protected IDToken idToken;
|
|
||||||
protected AccessToken token;
|
|
||||||
protected HttpFacade facade;
|
|
||||||
protected AuthChallenge challenge;
|
|
||||||
protected String refreshToken;
|
|
||||||
protected String strippedOauthParametersRequestUri;
|
|
||||||
|
|
||||||
public OAuthRequestAuthenticator(RequestAuthenticator requestAuthenticator, HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort, AdapterSessionStore tokenStore) {
|
|
||||||
this.reqAuthenticator = requestAuthenticator;
|
|
||||||
this.facade = facade;
|
|
||||||
this.deployment = deployment;
|
|
||||||
this.sslRedirectPort = deployment.getConfidentialPort() != -1 ? deployment.getConfidentialPort() : sslRedirectPort;
|
|
||||||
this.tokenStore = tokenStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthChallenge getChallenge() {
|
|
||||||
return challenge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTokenString() {
|
|
||||||
return tokenString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AccessToken getToken() {
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRefreshToken() {
|
|
||||||
return refreshToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getIdTokenString() {
|
|
||||||
return idTokenString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIdTokenString(String idTokenString) {
|
|
||||||
this.idTokenString = idTokenString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IDToken getIdToken() {
|
|
||||||
return idToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIdToken(IDToken idToken) {
|
|
||||||
this.idToken = idToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStrippedOauthParametersRequestUri() {
|
|
||||||
return strippedOauthParametersRequestUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrippedOauthParametersRequestUri(String strippedOauthParametersRequestUri) {
|
|
||||||
this.strippedOauthParametersRequestUri = strippedOauthParametersRequestUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String getRequestUrl() {
|
|
||||||
return facade.getRequest().getURI();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isRequestSecure() {
|
|
||||||
return facade.getRequest().isSecure();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected OIDCHttpFacade.Cookie getCookie(String cookieName) {
|
|
||||||
return facade.getRequest().getCookie(cookieName);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String getCookieValue(String cookieName) {
|
|
||||||
OIDCHttpFacade.Cookie cookie = getCookie(cookieName);
|
|
||||||
if (cookie == null) return null;
|
|
||||||
return cookie.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String getQueryParamValue(String paramName) {
|
|
||||||
return facade.getRequest().getQueryParamValue(paramName);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String getError() {
|
|
||||||
return getQueryParamValue(OAuth2Constants.ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String getCode() {
|
|
||||||
return getQueryParamValue(OAuth2Constants.CODE);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String getRedirectUri(String state) {
|
|
||||||
String url = getRequestUrl();
|
|
||||||
log.debugf("callback uri: %s", url);
|
|
||||||
|
|
||||||
if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
|
|
||||||
int port = sslRedirectPort();
|
|
||||||
if (port < 0) {
|
|
||||||
// disabled?
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
KeycloakUriBuilder secureUrl = KeycloakUriBuilder.fromUri(url).scheme("https").port(-1);
|
|
||||||
if (port != 443) secureUrl.port(port);
|
|
||||||
url = secureUrl.buildAsString();
|
|
||||||
}
|
|
||||||
|
|
||||||
String loginHint = getQueryParamValue("login_hint");
|
|
||||||
url = UriUtils.stripQueryParam(url,"login_hint");
|
|
||||||
|
|
||||||
String idpHint = getQueryParamValue(AdapterConstants.KC_IDP_HINT);
|
|
||||||
url = UriUtils.stripQueryParam(url, AdapterConstants.KC_IDP_HINT);
|
|
||||||
|
|
||||||
String scope = getQueryParamValue(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);
|
|
||||||
|
|
||||||
String uiLocales = getQueryParamValue(OAuth2Constants.UI_LOCALES_PARAM);
|
|
||||||
url = UriUtils.stripQueryParam(url, OAuth2Constants.UI_LOCALES_PARAM);
|
|
||||||
|
|
||||||
KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone()
|
|
||||||
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
|
|
||||||
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
|
|
||||||
.queryParam(OAuth2Constants.REDIRECT_URI, rewrittenRedirectUri(url))
|
|
||||||
.queryParam(OAuth2Constants.STATE, state)
|
|
||||||
.queryParam("login", "true");
|
|
||||||
if(loginHint != null && loginHint.length() > 0){
|
|
||||||
redirectUriBuilder.queryParam("login_hint",loginHint);
|
|
||||||
}
|
|
||||||
if (idpHint != null && idpHint.length() > 0) {
|
|
||||||
redirectUriBuilder.queryParam(AdapterConstants.KC_IDP_HINT,idpHint);
|
|
||||||
}
|
|
||||||
if (prompt != null && prompt.length() > 0) {
|
|
||||||
redirectUriBuilder.queryParam(OAuth2Constants.PROMPT, prompt);
|
|
||||||
}
|
|
||||||
if (maxAge != null && maxAge.length() > 0) {
|
|
||||||
redirectUriBuilder.queryParam(OAuth2Constants.MAX_AGE, maxAge);
|
|
||||||
}
|
|
||||||
if (uiLocales != null && uiLocales.length() > 0) {
|
|
||||||
redirectUriBuilder.queryParam(OAuth2Constants.UI_LOCALES_PARAM, uiLocales);
|
|
||||||
}
|
|
||||||
|
|
||||||
scope = TokenUtil.attachOIDCScope(scope);
|
|
||||||
redirectUriBuilder.queryParam(OAuth2Constants.SCOPE, scope);
|
|
||||||
|
|
||||||
return redirectUriBuilder.buildAsString();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected int sslRedirectPort() {
|
|
||||||
return sslRedirectPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String getStateCode() {
|
|
||||||
return AdapterUtils.generateId();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected AuthChallenge loginRedirect() {
|
|
||||||
final String state = getStateCode();
|
|
||||||
final String redirect = getRedirectUri(state);
|
|
||||||
if (redirect == null) {
|
|
||||||
return challenge(403, OIDCAuthenticationError.Reason.NO_REDIRECT_URI, null);
|
|
||||||
}
|
|
||||||
return new AuthChallenge() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getResponseCode() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean challenge(HttpFacade exchange) {
|
|
||||||
tokenStore.saveRequest();
|
|
||||||
log.debug("Sending redirect to login page: " + redirect);
|
|
||||||
exchange.getResponse().setStatus(302);
|
|
||||||
exchange.getResponse().setCookie(deployment.getStateCookieName(), state, "/", null, -1, deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr()), true);
|
|
||||||
exchange.getResponse().setHeader("Location", redirect);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected AuthChallenge checkStateCookie() {
|
|
||||||
OIDCHttpFacade.Cookie stateCookie = getCookie(deployment.getStateCookieName());
|
|
||||||
|
|
||||||
if (stateCookie == null) {
|
|
||||||
log.warn("No state cookie");
|
|
||||||
return challenge(400, OIDCAuthenticationError.Reason.INVALID_STATE_COOKIE, null);
|
|
||||||
}
|
|
||||||
// reset the cookie
|
|
||||||
log.debug("** reseting application state cookie");
|
|
||||||
facade.getResponse().resetCookie(deployment.getStateCookieName(), stateCookie.getPath());
|
|
||||||
String stateCookieValue = getCookieValue(deployment.getStateCookieName());
|
|
||||||
|
|
||||||
String state = getQueryParamValue(OAuth2Constants.STATE);
|
|
||||||
if (state == null) {
|
|
||||||
log.warn("state parameter was null");
|
|
||||||
return challenge(400, OIDCAuthenticationError.Reason.INVALID_STATE_COOKIE, null);
|
|
||||||
}
|
|
||||||
if (!state.equals(stateCookieValue)) {
|
|
||||||
log.warn("state parameter invalid");
|
|
||||||
log.warn("cookie: " + stateCookieValue);
|
|
||||||
log.warn("queryParam: " + state);
|
|
||||||
return challenge(400, OIDCAuthenticationError.Reason.INVALID_STATE_COOKIE, null);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthOutcome authenticate() {
|
|
||||||
String code = getCode();
|
|
||||||
if (code == null) {
|
|
||||||
log.debug("there was no code");
|
|
||||||
String error = getError();
|
|
||||||
if (error != null) {
|
|
||||||
// todo how do we send a response?
|
|
||||||
log.warn("There was an error: " + error);
|
|
||||||
challenge = challenge(400, OIDCAuthenticationError.Reason.OAUTH_ERROR, error);
|
|
||||||
return AuthOutcome.FAILED;
|
|
||||||
} else {
|
|
||||||
log.debug("redirecting to auth server");
|
|
||||||
challenge = loginRedirect();
|
|
||||||
return AuthOutcome.NOT_ATTEMPTED;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.debug("there was a code, resolving");
|
|
||||||
challenge = resolveCode(code);
|
|
||||||
if (challenge != null) {
|
|
||||||
return AuthOutcome.FAILED;
|
|
||||||
}
|
|
||||||
return AuthOutcome.AUTHENTICATED;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected AuthChallenge challenge(final int code, final OIDCAuthenticationError.Reason reason, final String description) {
|
|
||||||
return new AuthChallenge() {
|
|
||||||
@Override
|
|
||||||
public int getResponseCode() {
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean challenge(HttpFacade exchange) {
|
|
||||||
OIDCAuthenticationError error = new OIDCAuthenticationError(reason, description);
|
|
||||||
exchange.getRequest().setError(error);
|
|
||||||
exchange.getResponse().sendError(code);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start or continue the oauth login process.
|
|
||||||
* <p/>
|
|
||||||
* if code query parameter is not present, then browser is redirected to authUrl. The redirect URL will be
|
|
||||||
* the URL of the current request.
|
|
||||||
* <p/>
|
|
||||||
* If code query parameter is present, then an access token is obtained by invoking a secure request to the codeUrl.
|
|
||||||
* If the access token is obtained, the browser is again redirected to the current request URL, but any OAuth
|
|
||||||
* protocol specific query parameters are removed.
|
|
||||||
*
|
|
||||||
* @return null if an access token was obtained, otherwise a challenge is returned
|
|
||||||
*/
|
|
||||||
protected AuthChallenge resolveCode(String code) {
|
|
||||||
// abort if not HTTPS
|
|
||||||
if (!isRequestSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
|
|
||||||
log.error("Adapter requires SSL. Request: " + facade.getRequest().getURI());
|
|
||||||
return challenge(403, OIDCAuthenticationError.Reason.SSL_REQUIRED, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug("checking state cookie for after code");
|
|
||||||
AuthChallenge challenge = checkStateCookie();
|
|
||||||
if (challenge != null) return challenge;
|
|
||||||
|
|
||||||
AccessTokenResponse tokenResponse = null;
|
|
||||||
strippedOauthParametersRequestUri = rewrittenRedirectUri(stripOauthParametersFromRedirect());
|
|
||||||
|
|
||||||
try {
|
|
||||||
// For COOKIE store we don't have httpSessionId and single sign-out won't be available
|
|
||||||
String httpSessionId = deployment.getTokenStore() == TokenStore.SESSION ? reqAuthenticator.changeHttpSessionId(true) : null;
|
|
||||||
tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, strippedOauthParametersRequestUri, httpSessionId);
|
|
||||||
} catch (ServerRequest.HttpFailure failure) {
|
|
||||||
log.error("failed to turn code into token");
|
|
||||||
log.error("status from server: " + failure.getStatus());
|
|
||||||
if (failure.getError() != null && !failure.getError().trim().isEmpty()) {
|
|
||||||
log.error(" " + failure.getError());
|
|
||||||
}
|
|
||||||
return challenge(403, OIDCAuthenticationError.Reason.CODE_TO_TOKEN_FAILURE, null);
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("failed to turn code into token", e);
|
|
||||||
return challenge(403, OIDCAuthenticationError.Reason.CODE_TO_TOKEN_FAILURE, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenString = tokenResponse.getToken();
|
|
||||||
refreshToken = tokenResponse.getRefreshToken();
|
|
||||||
idTokenString = tokenResponse.getIdToken();
|
|
||||||
|
|
||||||
log.debug("Verifying tokens");
|
|
||||||
if (log.isTraceEnabled()) {
|
|
||||||
logToken("\taccess_token", tokenString);
|
|
||||||
logToken("\tid_token", idTokenString);
|
|
||||||
logToken("\trefresh_token", refreshToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
AdapterTokenVerifier.VerifiedTokens tokens = AdapterTokenVerifier.verifyTokens(tokenString, idTokenString, deployment);
|
|
||||||
token = tokens.getAccessToken();
|
|
||||||
idToken = tokens.getIdToken();
|
|
||||||
log.debug("Token Verification succeeded!");
|
|
||||||
} catch (VerificationException e) {
|
|
||||||
log.error("failed verification of token: " + e.getMessage());
|
|
||||||
return challenge(403, OIDCAuthenticationError.Reason.INVALID_TOKEN, null);
|
|
||||||
}
|
|
||||||
if (tokenResponse.getNotBeforePolicy() > deployment.getNotBefore()) {
|
|
||||||
deployment.updateNotBefore(tokenResponse.getNotBeforePolicy());
|
|
||||||
}
|
|
||||||
if (token.getIat() < deployment.getNotBefore()) {
|
|
||||||
log.error("Stale token");
|
|
||||||
return challenge(403, OIDCAuthenticationError.Reason.STALE_TOKEN, null);
|
|
||||||
}
|
|
||||||
log.debug("successful authenticated");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* strip out unwanted query parameters and redirect so bookmarks don't retain oauth protocol bits
|
|
||||||
*/
|
|
||||||
protected String stripOauthParametersFromRedirect() {
|
|
||||||
KeycloakUriBuilder builder = KeycloakUriBuilder.fromUri(facade.getRequest().getURI())
|
|
||||||
.replaceQueryParam(OAuth2Constants.CODE, null)
|
|
||||||
.replaceQueryParam(OAuth2Constants.STATE, null)
|
|
||||||
.replaceQueryParam(OAuth2Constants.SESSION_STATE, null)
|
|
||||||
.replaceQueryParam(OAuth2Constants.ISSUER, null);
|
|
||||||
return builder.buildAsString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String rewrittenRedirectUri(String originalUri) {
|
|
||||||
Map<String, String> rewriteRules = deployment.getRedirectRewriteRules();
|
|
||||||
if(rewriteRules != null && !rewriteRules.isEmpty()) {
|
|
||||||
try {
|
|
||||||
URL url = new URL(originalUri);
|
|
||||||
Map.Entry<String, String> rule = rewriteRules.entrySet().iterator().next();
|
|
||||||
StringBuilder redirectUriBuilder = new StringBuilder(url.getProtocol());
|
|
||||||
redirectUriBuilder.append("://"+ url.getAuthority());
|
|
||||||
redirectUriBuilder.append(url.getPath().replaceFirst(rule.getKey(), rule.getValue()));
|
|
||||||
return redirectUriBuilder.toString();
|
|
||||||
} catch (MalformedURLException ex) {
|
|
||||||
log.error("Not a valid request url");
|
|
||||||
throw new RuntimeException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return originalUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void logToken(String name, String token) {
|
|
||||||
try {
|
|
||||||
JWSInput jwsInput = new JWSInput(token);
|
|
||||||
String wireString = jwsInput.getWireString();
|
|
||||||
log.tracef("\t%s: %s", name, wireString.substring(0, wireString.lastIndexOf(".")) + ".signature");
|
|
||||||
} catch (JWSInputException e) {
|
|
||||||
log.errorf(e, "Failed to parse %s: %s", name, token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,64 +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.keycloak.adapters.spi.AuthenticationError;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object that describes the OIDC error that happened.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class OIDCAuthenticationError implements AuthenticationError {
|
|
||||||
public static enum Reason {
|
|
||||||
NO_BEARER_TOKEN,
|
|
||||||
NO_REDIRECT_URI,
|
|
||||||
INVALID_STATE_COOKIE,
|
|
||||||
OAUTH_ERROR,
|
|
||||||
SSL_REQUIRED,
|
|
||||||
CODE_TO_TOKEN_FAILURE,
|
|
||||||
INVALID_TOKEN,
|
|
||||||
STALE_TOKEN,
|
|
||||||
NO_AUTHORIZATION_HEADER,
|
|
||||||
NO_QUERY_PARAMETER_ACCESS_TOKEN
|
|
||||||
}
|
|
||||||
|
|
||||||
private Reason reason;
|
|
||||||
private String description;
|
|
||||||
|
|
||||||
public OIDCAuthenticationError(Reason reason, String description) {
|
|
||||||
this.reason = reason;
|
|
||||||
this.description = description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Reason getReason() {
|
|
||||||
return reason;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDescription() {
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "OIDCAuthenticationError [reason=" + reason + ", description=" + description + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,29 +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.keycloak.KeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.spi.KeycloakAccount;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public interface OidcKeycloakAccount extends KeycloakAccount {
|
|
||||||
KeycloakSecurityContext getKeycloakSecurityContext();
|
|
||||||
}
|
|
|
@ -1,267 +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 java.security.PublicKey;
|
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.keycloak.TokenVerifier;
|
|
||||||
import org.keycloak.adapters.rotation.AdapterTokenVerifier;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
import org.keycloak.adapters.spi.UserSessionManagement;
|
|
||||||
import org.keycloak.common.VerificationException;
|
|
||||||
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.protocol.oidc.client.authentication.ClientCredentialsProvider;
|
|
||||||
import org.keycloak.protocol.oidc.client.authentication.JWTClientCredentialsProvider;
|
|
||||||
import org.keycloak.representations.JsonWebToken;
|
|
||||||
import org.keycloak.constants.AdapterConstants;
|
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
|
||||||
import org.keycloak.representations.adapters.action.AdminAction;
|
|
||||||
import org.keycloak.representations.adapters.action.LogoutAction;
|
|
||||||
import org.keycloak.representations.adapters.action.PushNotBeforeAction;
|
|
||||||
import org.keycloak.representations.adapters.action.TestAvailabilityAction;
|
|
||||||
import org.keycloak.util.JsonSerialization;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class PreAuthActionsHandler {
|
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(PreAuthActionsHandler.class);
|
|
||||||
|
|
||||||
protected UserSessionManagement userSessionManagement;
|
|
||||||
protected AdapterDeploymentContext deploymentContext;
|
|
||||||
protected KeycloakDeployment deployment;
|
|
||||||
protected HttpFacade facade;
|
|
||||||
|
|
||||||
public PreAuthActionsHandler(UserSessionManagement userSessionManagement, AdapterDeploymentContext deploymentContext, HttpFacade facade) {
|
|
||||||
this.userSessionManagement = userSessionManagement;
|
|
||||||
this.deploymentContext = deploymentContext;
|
|
||||||
this.facade = facade;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean resolveDeployment() {
|
|
||||||
deployment = deploymentContext.resolveDeployment(facade);
|
|
||||||
if (!deployment.isConfigured()) {
|
|
||||||
log.warn("can't take request, adapter not configured");
|
|
||||||
facade.getResponse().sendError(403, "adapter not configured");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean handleRequest() {
|
|
||||||
String requestUri = facade.getRequest().getURI();
|
|
||||||
log.debugv("adminRequest {0}", requestUri);
|
|
||||||
if (preflightCors()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (requestUri.endsWith(AdapterConstants.K_LOGOUT)) {
|
|
||||||
if (!resolveDeployment()) return true;
|
|
||||||
handleLogout();
|
|
||||||
return true;
|
|
||||||
} else if (requestUri.endsWith(AdapterConstants.K_PUSH_NOT_BEFORE)) {
|
|
||||||
if (!resolveDeployment()) return true;
|
|
||||||
handlePushNotBefore();
|
|
||||||
return true;
|
|
||||||
} else if (requestUri.endsWith(AdapterConstants.K_TEST_AVAILABLE)) {
|
|
||||||
if (!resolveDeployment()) return true;
|
|
||||||
handleTestAvailable();
|
|
||||||
return true;
|
|
||||||
} else if (requestUri.endsWith(AdapterConstants.K_JWKS)) {
|
|
||||||
if (!resolveDeployment()) return true;
|
|
||||||
handleJwksRequest();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean preflightCors() {
|
|
||||||
// don't need to resolve deployment on cors requests. Just need to know local cors config.
|
|
||||||
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
|
|
||||||
if (!deployment.isCors()) return false;
|
|
||||||
log.debugv("checkCorsPreflight {0}", facade.getRequest().getURI());
|
|
||||||
if (!facade.getRequest().getMethod().equalsIgnoreCase("OPTIONS")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN);
|
|
||||||
if (origin == null) {
|
|
||||||
log.debug("checkCorsPreflight: no origin header");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
log.debug("Preflight request returning");
|
|
||||||
facade.getResponse().setStatus(200);
|
|
||||||
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
|
|
||||||
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
|
|
||||||
String requestMethods = facade.getRequest().getHeader(CorsHeaders.ACCESS_CONTROL_REQUEST_METHOD);
|
|
||||||
if (requestMethods != null) {
|
|
||||||
if (deployment.getCorsAllowedMethods() != null) {
|
|
||||||
requestMethods = deployment.getCorsAllowedMethods();
|
|
||||||
}
|
|
||||||
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethods);
|
|
||||||
}
|
|
||||||
String allowHeaders = facade.getRequest().getHeader(CorsHeaders.ACCESS_CONTROL_REQUEST_HEADERS);
|
|
||||||
if (allowHeaders != null) {
|
|
||||||
if (deployment.getCorsAllowedHeaders() != null) {
|
|
||||||
allowHeaders = deployment.getCorsAllowedHeaders();
|
|
||||||
}
|
|
||||||
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_HEADERS, allowHeaders);
|
|
||||||
}
|
|
||||||
if (deployment.getCorsMaxAge() > -1) {
|
|
||||||
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_MAX_AGE, Integer.toString(deployment.getCorsMaxAge()));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void handleLogout() {
|
|
||||||
if (log.isTraceEnabled()) {
|
|
||||||
log.trace("K_LOGOUT sent");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
JWSInput token = verifyAdminRequest();
|
|
||||||
if (token == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
LogoutAction action = JsonSerialization.readValue(token.getContent(), LogoutAction.class);
|
|
||||||
if (!validateAction(action)) return;
|
|
||||||
if (action.getAdapterSessionIds() != null) {
|
|
||||||
userSessionManagement.logoutHttpSessions(action.getAdapterSessionIds());
|
|
||||||
} else {
|
|
||||||
log.debugf("logout of all sessions for application '%s'", action.getResource());
|
|
||||||
if (action.getNotBefore() > deployment.getNotBefore()) {
|
|
||||||
deployment.updateNotBefore(action.getNotBefore());
|
|
||||||
}
|
|
||||||
userSessionManagement.logoutAll();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
protected void handlePushNotBefore() {
|
|
||||||
if (log.isTraceEnabled()) {
|
|
||||||
log.trace("K_PUSH_NOT_BEFORE sent");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
JWSInput token = verifyAdminRequest();
|
|
||||||
if (token == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
PushNotBeforeAction action = JsonSerialization.readValue(token.getContent(), PushNotBeforeAction.class);
|
|
||||||
if (!validateAction(action)) return;
|
|
||||||
deployment.updateNotBefore(action.getNotBefore());
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void handleTestAvailable() {
|
|
||||||
if (log.isTraceEnabled()) {
|
|
||||||
log.trace("K_TEST_AVAILABLE sent");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
JWSInput token = verifyAdminRequest();
|
|
||||||
if (token == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TestAvailabilityAction action = JsonSerialization.readValue(token.getContent(), TestAvailabilityAction.class);
|
|
||||||
validateAction(action);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected JWSInput verifyAdminRequest() throws Exception {
|
|
||||||
if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
|
|
||||||
log.warn("SSL is required for adapter admin action");
|
|
||||||
facade.getResponse().sendError(403, "ssl required");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String token = StreamUtil.readString(facade.getRequest().getInputStream());
|
|
||||||
if (token == null) {
|
|
||||||
log.warn("admin request failed, no token");
|
|
||||||
facade.getResponse().sendError(403, "no token");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Check just signature. Other things checked in validateAction
|
|
||||||
TokenVerifier tokenVerifier = AdapterTokenVerifier.createVerifier(token, deployment, false, JsonWebToken.class);
|
|
||||||
tokenVerifier.verify();
|
|
||||||
return new JWSInput(token);
|
|
||||||
} catch (VerificationException ignore) {
|
|
||||||
log.warn("admin request failed, unable to verify token: " + ignore.getMessage());
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debug(ignore.getMessage(), ignore);
|
|
||||||
}
|
|
||||||
|
|
||||||
facade.getResponse().sendError(403, "token failed verification");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected boolean validateAction(AdminAction action) {
|
|
||||||
if (!action.validate()) {
|
|
||||||
log.warn("admin request failed, not validated" + action.getAction());
|
|
||||||
facade.getResponse().sendError(400, "Not validated");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (action.isExpired()) {
|
|
||||||
log.warn("admin request failed, expired token");
|
|
||||||
facade.getResponse().sendError(400, "Expired token");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!deployment.getResourceName().equals(action.getResource())) {
|
|
||||||
log.warn("Resource name does not match");
|
|
||||||
facade.getResponse().sendError(400, "Resource name does not match");
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,60 +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>
|
|
||||||
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
|
|
||||||
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class QueryParameterTokenRequestAuthenticator extends BearerTokenRequestAuthenticator {
|
|
||||||
public static final String ACCESS_TOKEN = "access_token";
|
|
||||||
protected Logger log = Logger.getLogger(QueryParameterTokenRequestAuthenticator.class);
|
|
||||||
|
|
||||||
public QueryParameterTokenRequestAuthenticator(KeycloakDeployment deployment) {
|
|
||||||
super(deployment);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthOutcome authenticate(HttpFacade exchange) {
|
|
||||||
if(!deployment.isOAuthQueryParameterEnabled()) {
|
|
||||||
return AuthOutcome.NOT_ATTEMPTED;
|
|
||||||
}
|
|
||||||
tokenString = null;
|
|
||||||
tokenString = getAccessTokenFromQueryParameter(exchange);
|
|
||||||
if (tokenString == null || tokenString.trim().isEmpty()) {
|
|
||||||
log.debug("Token is not present in query");
|
|
||||||
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_QUERY_PARAMETER_ACCESS_TOKEN, null, null);
|
|
||||||
return AuthOutcome.NOT_ATTEMPTED;
|
|
||||||
}
|
|
||||||
return (authenticateToken(exchange, tokenString));
|
|
||||||
}
|
|
||||||
|
|
||||||
String getAccessTokenFromQueryParameter(HttpFacade exchange) {
|
|
||||||
try {
|
|
||||||
if (exchange != null && exchange.getRequest() != null) {
|
|
||||||
return exchange.getRequest().getQueryParamValue(ACCESS_TOKEN);
|
|
||||||
}
|
|
||||||
} catch (Exception ignore) {
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,240 +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 java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.keycloak.KeycloakPrincipal;
|
|
||||||
import org.keycloak.adapters.spi.AuthChallenge;
|
|
||||||
import org.keycloak.adapters.spi.AuthOutcome;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public abstract class RequestAuthenticator {
|
|
||||||
protected static Logger log = Logger.getLogger(RequestAuthenticator.class);
|
|
||||||
protected HttpFacade facade;
|
|
||||||
protected AuthChallenge challenge;
|
|
||||||
|
|
||||||
protected KeycloakDeployment deployment;
|
|
||||||
protected AdapterTokenStore tokenStore;
|
|
||||||
protected int sslRedirectPort;
|
|
||||||
|
|
||||||
public RequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, AdapterTokenStore tokenStore, int sslRedirectPort) {
|
|
||||||
this.facade = facade;
|
|
||||||
this.deployment = deployment;
|
|
||||||
this.tokenStore = tokenStore;
|
|
||||||
this.sslRedirectPort = sslRedirectPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment) {
|
|
||||||
this.facade = facade;
|
|
||||||
this.deployment = deployment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthChallenge getChallenge() {
|
|
||||||
return challenge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthOutcome authenticate() {
|
|
||||||
if (log.isTraceEnabled()) {
|
|
||||||
log.trace("--> authenticate()");
|
|
||||||
}
|
|
||||||
|
|
||||||
BearerTokenRequestAuthenticator bearer = createBearerTokenAuthenticator();
|
|
||||||
if (log.isTraceEnabled()) {
|
|
||||||
log.trace("try bearer");
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthOutcome outcome = bearer.authenticate(facade);
|
|
||||||
if (outcome == AuthOutcome.FAILED) {
|
|
||||||
challenge = bearer.getChallenge();
|
|
||||||
log.debug("Bearer FAILED");
|
|
||||||
return AuthOutcome.FAILED;
|
|
||||||
} else if (outcome == AuthOutcome.AUTHENTICATED) {
|
|
||||||
if (verifySSL()) return AuthOutcome.FAILED;
|
|
||||||
completeAuthentication(bearer, "KEYCLOAK");
|
|
||||||
log.debug("Bearer AUTHENTICATED");
|
|
||||||
return AuthOutcome.AUTHENTICATED;
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryParameterTokenRequestAuthenticator queryParamAuth = createQueryParameterTokenRequestAuthenticator();
|
|
||||||
if (log.isTraceEnabled()) {
|
|
||||||
log.trace("try query parameter 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()) {
|
|
||||||
BasicAuthRequestAuthenticator basicAuth = createBasicAuthAuthenticator();
|
|
||||||
if (log.isTraceEnabled()) {
|
|
||||||
log.trace("try basic auth");
|
|
||||||
}
|
|
||||||
|
|
||||||
outcome = basicAuth.authenticate(facade);
|
|
||||||
if (outcome == AuthOutcome.FAILED) {
|
|
||||||
challenge = basicAuth.getChallenge();
|
|
||||||
log.debug("BasicAuth FAILED");
|
|
||||||
return AuthOutcome.FAILED;
|
|
||||||
} else if (outcome == AuthOutcome.AUTHENTICATED) {
|
|
||||||
if (verifySSL()) return AuthOutcome.FAILED;
|
|
||||||
log.debug("BasicAuth AUTHENTICATED");
|
|
||||||
completeAuthentication(basicAuth, "BASIC");
|
|
||||||
return AuthOutcome.AUTHENTICATED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deployment.isBearerOnly()) {
|
|
||||||
challenge = bearer.getChallenge();
|
|
||||||
log.debug("NOT_ATTEMPTED: bearer only");
|
|
||||||
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()) {
|
|
||||||
log.trace("try oauth");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tokenStore.isCached(this)) {
|
|
||||||
if (verifySSL()) return AuthOutcome.FAILED;
|
|
||||||
log.debug("AUTHENTICATED: was cached");
|
|
||||||
return AuthOutcome.AUTHENTICATED;
|
|
||||||
}
|
|
||||||
|
|
||||||
OAuthRequestAuthenticator oauth = createOAuthAuthenticator();
|
|
||||||
outcome = oauth.authenticate();
|
|
||||||
if (outcome == AuthOutcome.FAILED) {
|
|
||||||
challenge = oauth.getChallenge();
|
|
||||||
return AuthOutcome.FAILED;
|
|
||||||
} else if (outcome == AuthOutcome.NOT_ATTEMPTED) {
|
|
||||||
challenge = oauth.getChallenge();
|
|
||||||
return AuthOutcome.NOT_ATTEMPTED;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (verifySSL()) return AuthOutcome.FAILED;
|
|
||||||
|
|
||||||
completeAuthentication(oauth);
|
|
||||||
|
|
||||||
// redirect to strip out access code and state query parameters
|
|
||||||
facade.getResponse().setHeader("Location", oauth.getStrippedOauthParametersRequestUri());
|
|
||||||
facade.getResponse().setStatus(302);
|
|
||||||
facade.getResponse().end();
|
|
||||||
|
|
||||||
log.debug("AUTHENTICATED");
|
|
||||||
return AuthOutcome.AUTHENTICATED;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean verifySSL() {
|
|
||||||
if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
|
|
||||||
log.warnf("SSL is required to authenticate. Remote address %s is secure: %s, SSL required for: %s .",
|
|
||||||
facade.getRequest().getRemoteAddr(), facade.getRequest().isSecure(), deployment.getSslRequired().name());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
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 BearerTokenRequestAuthenticator createBearerTokenAuthenticator() {
|
|
||||||
return new BearerTokenRequestAuthenticator(deployment);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected BasicAuthRequestAuthenticator createBasicAuthAuthenticator() {
|
|
||||||
return new BasicAuthRequestAuthenticator(deployment);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected QueryParameterTokenRequestAuthenticator createQueryParameterTokenRequestAuthenticator() {
|
|
||||||
return new QueryParameterTokenRequestAuthenticator(deployment);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void completeAuthentication(OAuthRequestAuthenticator oauth) {
|
|
||||||
RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, tokenStore, oauth.getTokenString(), oauth.getToken(), oauth.getIdTokenString(), oauth.getIdToken(), oauth.getRefreshToken());
|
|
||||||
final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<>(AdapterUtils.getPrincipalName(deployment, oauth.getToken()), session);
|
|
||||||
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 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
|
|
||||||
*
|
|
||||||
* @param create
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
protected abstract String changeHttpSessionId(boolean create);
|
|
||||||
|
|
||||||
protected void completeAuthentication(BearerTokenRequestAuthenticator bearer, String method) {
|
|
||||||
RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, null, bearer.getTokenString(), bearer.getToken(), null, null, null);
|
|
||||||
final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<>(AdapterUtils.getPrincipalName(deployment, bearer.getToken()), session);
|
|
||||||
completeBearerAuthentication(principal, method);
|
|
||||||
log.debugv("User ''{0}'' invoking ''{1}'' on client ''{2}''", principal.getName(), facade.getRequest().getURI(), deployment.getResourceName());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,139 +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.client.HttpClient;
|
|
||||||
import org.apache.http.client.methods.Configurable;
|
|
||||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
|
||||||
import org.hamcrest.CoreMatchers;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.adapters.rotation.HardcodedPublicKeyLocator;
|
|
||||||
import org.keycloak.adapters.rotation.JWKPublicKeyLocator;
|
|
||||||
import org.keycloak.common.enums.RelativeUrlsUsed;
|
|
||||||
import org.keycloak.common.enums.SslRequired;
|
|
||||||
import org.keycloak.common.util.PemUtils;
|
|
||||||
import org.keycloak.enums.TokenStore;
|
|
||||||
import org.keycloak.protocol.oidc.client.authentication.ClientIdAndSecretCredentialsProvider;
|
|
||||||
import org.keycloak.protocol.oidc.client.authentication.JWTClientCredentialsProvider;
|
|
||||||
import org.keycloak.protocol.oidc.client.authentication.JWTClientSecretCredentialsProvider;
|
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
|
||||||
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
|
|
||||||
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
|
|
||||||
*/
|
|
||||||
public class KeycloakDeploymentBuilderTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void load() {
|
|
||||||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak.json"));
|
|
||||||
assertEquals("demo", deployment.getRealm());
|
|
||||||
assertEquals("customer-portal", deployment.getResourceName());
|
|
||||||
|
|
||||||
assertTrue(deployment.getPublicKeyLocator() instanceof HardcodedPublicKeyLocator);
|
|
||||||
assertEquals(PemUtils.decodePublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"),
|
|
||||||
deployment.getPublicKeyLocator().getPublicKey(null, deployment));
|
|
||||||
|
|
||||||
assertEquals("https://localhost:8443/auth", deployment.getAuthServerBaseUrl());
|
|
||||||
assertEquals(SslRequired.EXTERNAL, deployment.getSslRequired());
|
|
||||||
assertTrue(deployment.isUseResourceRoleMappings());
|
|
||||||
assertTrue(deployment.isCors());
|
|
||||||
assertEquals(1000, deployment.getCorsMaxAge());
|
|
||||||
assertEquals("POST, PUT, DELETE, GET", deployment.getCorsAllowedMethods());
|
|
||||||
assertEquals("X-Custom, X-Custom2", deployment.getCorsAllowedHeaders());
|
|
||||||
assertEquals("X-Custom3, X-Custom4", deployment.getCorsExposedHeaders());
|
|
||||||
assertTrue(deployment.isBearerOnly());
|
|
||||||
assertTrue(deployment.isPublicClient());
|
|
||||||
assertTrue(deployment.isEnableBasicAuth());
|
|
||||||
assertTrue(deployment.isExposeToken());
|
|
||||||
assertFalse(deployment.isOAuthQueryParameterEnabled());
|
|
||||||
assertEquals("234234-234234-234234", deployment.getResourceCredentials().get("secret"));
|
|
||||||
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
|
|
||||||
HttpClient client = deployment.getClient();
|
|
||||||
int maxPoolConnections = -1;
|
|
||||||
Field connManager = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
connManager = client.getClass().getDeclaredField("connManager");
|
|
||||||
connManager.setAccessible(true);
|
|
||||||
maxPoolConnections = ((PoolingHttpClientConnectionManager) connManager.get(client)).getMaxTotal();
|
|
||||||
} catch (Exception cause) {
|
|
||||||
throw new RuntimeException("Failed to get max pool connections", cause);
|
|
||||||
} finally {
|
|
||||||
connManager.setAccessible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
assertEquals(20, maxPoolConnections);
|
|
||||||
assertEquals(RelativeUrlsUsed.NEVER, deployment.getRelativeUrls());
|
|
||||||
assertTrue(deployment.isAlwaysRefreshToken());
|
|
||||||
assertTrue(deployment.isRegisterNodeAtStartup());
|
|
||||||
assertEquals(1000, deployment.getRegisterNodePeriod());
|
|
||||||
assertEquals(TokenStore.COOKIE, deployment.getTokenStore());
|
|
||||||
assertEquals("email", deployment.getPrincipalAttribute());
|
|
||||||
assertEquals(10, deployment.getTokenMinimumTimeToLive());
|
|
||||||
assertEquals(20, deployment.getMinTimeBetweenJwksRequests());
|
|
||||||
assertEquals(120, deployment.getPublicKeyCacheTtl());
|
|
||||||
assertEquals("/api/$1", deployment.getRedirectRewriteRules().get("^/wsmaster/api/(.*)$"));
|
|
||||||
assertTrue(deployment.isVerifyTokenAudience());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void loadNoClientCredentials() {
|
|
||||||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-no-credentials.json"));
|
|
||||||
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
|
|
||||||
|
|
||||||
assertTrue(deployment.getPublicKeyLocator() instanceof JWKPublicKeyLocator);
|
|
||||||
assertEquals(10, deployment.getMinTimeBetweenJwksRequests());
|
|
||||||
assertEquals(86400, deployment.getPublicKeyCacheTtl());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void loadJwtCredentials() {
|
|
||||||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-jwt.json"));
|
|
||||||
assertEquals(JWTClientCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void loadSecretJwtCredentials() {
|
|
||||||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-secret-jwt.json"));
|
|
||||||
assertEquals(JWTClientSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void loadHttpClientTimeoutConfiguration() {
|
|
||||||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-http-client.json"));
|
|
||||||
assertThat(deployment, CoreMatchers.notNullValue());
|
|
||||||
|
|
||||||
HttpClient client = deployment.getClient();
|
|
||||||
assertThat(client, CoreMatchers.notNullValue());
|
|
||||||
|
|
||||||
long socketTimeout = ((Configurable) client).getConfig().getSocketTimeout();
|
|
||||||
long connectionTimeout = ((Configurable) client).getConfig().getConnectTimeout();
|
|
||||||
|
|
||||||
assertThat(socketTimeout, CoreMatchers.is(2000L));
|
|
||||||
assertThat(connectionTimeout, CoreMatchers.is(6000L));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.keycloak.adapters;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
|
||||||
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
|
|
||||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
|
|
||||||
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
|
|
||||||
*/
|
|
||||||
public class KeycloakDeploymentTest {
|
|
||||||
@Test
|
|
||||||
public void shouldNotEnableOAuthQueryParamWhenIgnoreIsTrue() {
|
|
||||||
KeycloakDeployment keycloakDeployment = new KeycloakDeploymentMock();
|
|
||||||
keycloakDeployment.setIgnoreOAuthQueryParameter(true);
|
|
||||||
assertFalse(keycloakDeployment.isOAuthQueryParameterEnabled());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldEnableOAuthQueryParamWhenIgnoreIsFalse() {
|
|
||||||
KeycloakDeployment keycloakDeployment = new KeycloakDeploymentMock();
|
|
||||||
keycloakDeployment.setIgnoreOAuthQueryParameter(false);
|
|
||||||
assertTrue(keycloakDeployment.isOAuthQueryParameterEnabled());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldEnableOAuthQueryParamWhenIgnoreNotSet() {
|
|
||||||
KeycloakDeployment keycloakDeployment = new KeycloakDeploymentMock();
|
|
||||||
|
|
||||||
assertTrue(keycloakDeployment.isOAuthQueryParameterEnabled());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void stripDefaultPorts() {
|
|
||||||
KeycloakDeployment keycloakDeployment = new KeycloakDeploymentMock();
|
|
||||||
keycloakDeployment.setRealm("test");
|
|
||||||
AdapterConfig config = new AdapterConfig();
|
|
||||||
config.setAuthServerUrl("http://localhost:80/auth");
|
|
||||||
|
|
||||||
keycloakDeployment.setAuthServerBaseUrl(config);
|
|
||||||
|
|
||||||
assertEquals("http://localhost/auth", keycloakDeployment.getAuthServerBaseUrl());
|
|
||||||
|
|
||||||
config.setAuthServerUrl("https://localhost:443/auth");
|
|
||||||
keycloakDeployment.setAuthServerBaseUrl(config);
|
|
||||||
|
|
||||||
assertEquals("https://localhost/auth", keycloakDeployment.getAuthServerBaseUrl());
|
|
||||||
}
|
|
||||||
|
|
||||||
class KeycloakDeploymentMock extends KeycloakDeployment {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected OIDCConfigurationRepresentation getOidcConfiguration(String discoveryUrl) throws Exception {
|
|
||||||
String base = KeycloakUriBuilder.fromUri(discoveryUrl).replacePath("/auth").build().toString();
|
|
||||||
|
|
||||||
OIDCConfigurationRepresentation rep = new OIDCConfigurationRepresentation();
|
|
||||||
rep.setAuthorizationEndpoint(base + "/realms/test/authz");
|
|
||||||
rep.setTokenEndpoint(base + "/realms/test/tokens");
|
|
||||||
rep.setIssuer(base + "/realms/test");
|
|
||||||
rep.setJwksUri(base + "/realms/test/jwks");
|
|
||||||
rep.setLogoutEndpoint(base + "/realms/test/logout");
|
|
||||||
return rep;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,114 +0,0 @@
|
||||||
package org.keycloak.adapters;
|
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.KeycloakPrincipal;
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
|
||||||
import org.keycloak.jose.jws.JWSBuilder;
|
|
||||||
import org.keycloak.representations.AccessToken;
|
|
||||||
import org.keycloak.representations.IDToken;
|
|
||||||
import org.keycloak.representations.oidc.TokenMetadataRepresentation;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.KeyPairGenerator;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void sameIssuedAtAsNotBeforeIsActiveKEYCLOAK10013() {
|
|
||||||
KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
|
|
||||||
keycloakDeployment.setNotBefore(5000);
|
|
||||||
|
|
||||||
TokenMetadataRepresentation token = new TokenMetadataRepresentation();
|
|
||||||
token.setActive(true);
|
|
||||||
token.iat(4999L);
|
|
||||||
|
|
||||||
RefreshableKeycloakSecurityContext sut = new RefreshableKeycloakSecurityContext(keycloakDeployment,null,null,token,null, null, null);
|
|
||||||
|
|
||||||
assertFalse(sut.isActive());
|
|
||||||
|
|
||||||
token.iat(5000L);
|
|
||||||
assertTrue(sut.isActive());
|
|
||||||
}
|
|
||||||
|
|
||||||
private AccessToken createSimpleToken() {
|
|
||||||
AccessToken token = new AccessToken();
|
|
||||||
token.id("111");
|
|
||||||
token.issuer("http://localhost:8080/auth/acme");
|
|
||||||
token.addAccess("foo").addRole("admin");
|
|
||||||
token.addAccess("bar").addRole("user");
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSerialization() throws Exception {
|
|
||||||
AccessToken token = createSimpleToken();
|
|
||||||
IDToken idToken = new IDToken();
|
|
||||||
|
|
||||||
idToken.setEmail("joe@email.cz");
|
|
||||||
|
|
||||||
KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
|
|
||||||
|
|
||||||
String encoded = new JWSBuilder()
|
|
||||||
.jsonContent(token)
|
|
||||||
.rsa256(keyPair.getPrivate());
|
|
||||||
String encodedIdToken = new JWSBuilder()
|
|
||||||
.jsonContent(idToken)
|
|
||||||
.rsa256(keyPair.getPrivate());
|
|
||||||
|
|
||||||
KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
|
|
||||||
keycloakDeployment.setNotBefore(5000);
|
|
||||||
|
|
||||||
KeycloakSecurityContext ctx = new RefreshableKeycloakSecurityContext(keycloakDeployment,null, encoded, token,encodedIdToken, null, null);
|
|
||||||
KeycloakPrincipal principal = new KeycloakPrincipal("joe", ctx);
|
|
||||||
|
|
||||||
// Serialize
|
|
||||||
ByteArrayOutputStream bso = new ByteArrayOutputStream();
|
|
||||||
ObjectOutputStream oos = new ObjectOutputStream(bso);
|
|
||||||
oos.writeObject(principal);
|
|
||||||
oos.close();
|
|
||||||
|
|
||||||
// Deserialize
|
|
||||||
byte[] bytes = bso.toByteArray();
|
|
||||||
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
|
||||||
ObjectInputStream ois = new ObjectInputStream(bis);
|
|
||||||
principal = (KeycloakPrincipal)ois.readObject();
|
|
||||||
ctx = principal.getKeycloakSecurityContext();
|
|
||||||
token = ctx.getToken();
|
|
||||||
idToken = ctx.getIdToken();
|
|
||||||
|
|
||||||
System.out.println("Size of serialized principal: " + bytes.length);
|
|
||||||
|
|
||||||
Assert.assertEquals(encoded, ctx.getTokenString());
|
|
||||||
Assert.assertEquals(encodedIdToken, ctx.getIdTokenString());
|
|
||||||
Assert.assertEquals("111", token.getId());
|
|
||||||
Assert.assertEquals("111", token.getId());
|
|
||||||
Assert.assertTrue(token.getResourceAccess("foo").isUserInRole("admin"));
|
|
||||||
Assert.assertTrue(token.getResourceAccess("bar").isUserInRole("user"));
|
|
||||||
Assert.assertEquals("joe@email.cz", idToken.getEmail());
|
|
||||||
Assert.assertEquals("acme", ctx.getRealm());
|
|
||||||
ois.close();
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
"realm": "demo",
|
|
||||||
"resource": "customer-portal",
|
|
||||||
"auth-server-url": "https://localhost:8443/auth",
|
|
||||||
"public-client": true,
|
|
||||||
"socket-timeout-millis": 2000,
|
|
||||||
"connection-timeout-millis": 6000
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"realm": "demo",
|
|
||||||
"resource": "customer-portal",
|
|
||||||
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url": "https://localhost:8443/auth",
|
|
||||||
"ssl-required": "external",
|
|
||||||
"credentials": {
|
|
||||||
"jwt": {
|
|
||||||
"client-keystore-file": "classpath:keystore.jks",
|
|
||||||
"client-keystore-password": "storepass"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"realm": "demo",
|
|
||||||
"resource": "customer-portal",
|
|
||||||
"auth-server-url": "https://localhost:8443/auth",
|
|
||||||
"public-client": true,
|
|
||||||
"expose-token": true
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"realm": "demo",
|
|
||||||
"resource": "customer-portal",
|
|
||||||
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url": "https://localhost:8443/auth",
|
|
||||||
"ssl-required": "external",
|
|
||||||
"credentials": {
|
|
||||||
"secret-jwt": {
|
|
||||||
"secret": "234234-234234-234234"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
{
|
|
||||||
"realm": "demo",
|
|
||||||
"resource": "customer-portal",
|
|
||||||
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url": "https://localhost:8443/auth",
|
|
||||||
"ssl-required": "external",
|
|
||||||
"use-resource-role-mappings": true,
|
|
||||||
"enable-cors": true,
|
|
||||||
"cors-max-age": 1000,
|
|
||||||
"cors-allowed-methods": "POST, PUT, DELETE, GET",
|
|
||||||
"cors-allowed-headers": "X-Custom, X-Custom2",
|
|
||||||
"cors-exposed-headers": "X-Custom3, X-Custom4",
|
|
||||||
"bearer-only": true,
|
|
||||||
"public-client": true,
|
|
||||||
"enable-basic-auth": true,
|
|
||||||
"expose-token": true,
|
|
||||||
"credentials": {
|
|
||||||
"secret": "234234-234234-234234"
|
|
||||||
},
|
|
||||||
"connection-pool-size": 20,
|
|
||||||
"disable-trust-manager": true,
|
|
||||||
"allow-any-hostname": true,
|
|
||||||
"truststore": "classpath:/cacerts.jks",
|
|
||||||
"truststore-password": "changeit",
|
|
||||||
"client-keystore": "classpath:/keystore.jks",
|
|
||||||
"client-keystore-password": "storepass",
|
|
||||||
"client-key-password": "keypass",
|
|
||||||
"always-refresh-token": true,
|
|
||||||
"register-node-at-startup": true,
|
|
||||||
"register-node-period": 1000,
|
|
||||||
"token-store": "cookie",
|
|
||||||
"principal-attribute": "email",
|
|
||||||
"token-minimum-time-to-live": 10,
|
|
||||||
"min-time-between-jwks-requests": 20,
|
|
||||||
"public-key-cache-ttl": 120,
|
|
||||||
"ignore-oauth-query-parameter": true,
|
|
||||||
"verify-token-audience": true,
|
|
||||||
"redirect-rewrite-rules" : {
|
|
||||||
"^/wsmaster/api/(.*)$" : "/api/$1"
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
|
@ -31,7 +31,6 @@
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>adapter-core</module>
|
|
||||||
<module>js</module>
|
<module>js</module>
|
||||||
</modules>
|
</modules>
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -41,11 +41,6 @@
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-saml-core</artifactId>
|
<artifactId>keycloak-saml-core</artifactId>
|
||||||
|
|
|
@ -31,11 +31,6 @@
|
||||||
<description/>
|
<description/>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-saml-core</artifactId>
|
<artifactId>keycloak-saml-core</artifactId>
|
||||||
|
|
|
@ -39,11 +39,6 @@
|
||||||
<artifactId>keycloak-core</artifactId>
|
<artifactId>keycloak-core</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-crypto-default</artifactId>
|
<artifactId>keycloak-crypto-default</artifactId>
|
||||||
|
|
|
@ -72,16 +72,6 @@
|
||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
|
||||||
<exclusions>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>*</groupId>
|
|
||||||
<artifactId>*</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
</exclusions>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-jboss-adapter-core</artifactId>
|
<artifactId>keycloak-jboss-adapter-core</artifactId>
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!--
|
|
||||||
~ 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.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<module xmlns="urn:jboss:module:1.3" name="org.keycloak.keycloak-adapter-core">
|
|
||||||
<resources>
|
|
||||||
<artifact name="${org.keycloak:keycloak-adapter-core}"/>
|
|
||||||
</resources>
|
|
||||||
<dependencies>
|
|
||||||
<module name="javax.api"/>
|
|
||||||
<module name="com.fasterxml.jackson.core.jackson-annotations"/>
|
|
||||||
<module name="com.fasterxml.jackson.core.jackson-core"/>
|
|
||||||
<module name="com.fasterxml.jackson.core.jackson-databind"/>
|
|
||||||
<module name="com.fasterxml.jackson.jaxrs.jackson-jaxrs-json-provider"/>
|
|
||||||
<module name="org.apache.httpcomponents"/>
|
|
||||||
<module name="org.jboss.logging"/>
|
|
||||||
<module name="org.keycloak.keycloak-adapter-spi"/>
|
|
||||||
<module name="org.keycloak.keycloak-common"/>
|
|
||||||
<module name="org.keycloak.keycloak-core"/>
|
|
||||||
<module name="org.keycloak.keycloak-crypto-default" services="import"/>
|
|
||||||
<module name="org.keycloak.keycloak-authz-client"/>
|
|
||||||
<module name="org.keycloak.keycloak-policy-enforcer"/>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</module>
|
|
5
pom.xml
5
pom.xml
|
@ -970,11 +970,6 @@
|
||||||
<artifactId>keycloak-adapter-spi</artifactId>
|
<artifactId>keycloak-adapter-spi</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-admin-client-jee</artifactId>
|
<artifactId>keycloak-admin-client-jee</artifactId>
|
||||||
|
|
|
@ -30,9 +30,9 @@
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak.testsuite</groupId>
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
<artifactId>integration-arquillian-util</artifactId>
|
||||||
<scope>provided</scope>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
|
|
|
@ -58,9 +58,9 @@
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak.testsuite</groupId>
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
<artifactId>integration-arquillian-util</artifactId>
|
||||||
<scope>provided</scope>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.httpcomponents</groupId>
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
|
|
@ -31,8 +31,9 @@
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak.testsuite</groupId>
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
<artifactId>integration-arquillian-util</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
|
|
|
@ -26,8 +26,9 @@
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak.testsuite</groupId>
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
<artifactId>integration-arquillian-util</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
|
|
|
@ -35,7 +35,6 @@ import org.junit.Before;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.adapters.OIDCAuthenticationError;
|
|
||||||
import org.keycloak.admin.client.resource.ClientResource;
|
import org.keycloak.admin.client.resource.ClientResource;
|
||||||
import org.keycloak.admin.client.resource.RealmResource;
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
|
@ -661,14 +660,14 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
|
||||||
assertEquals(401, response.getStatus());
|
assertEquals(401, response.getStatus());
|
||||||
String errorPageResponse = response.readEntity(String.class);
|
String errorPageResponse = response.readEntity(String.class);
|
||||||
assertThat(errorPageResponse, containsString("Error Page"));
|
assertThat(errorPageResponse, containsString("Error Page"));
|
||||||
assertThat(errorPageResponse, containsString(OIDCAuthenticationError.Reason.NO_BEARER_TOKEN.toString()));
|
assertThat(errorPageResponse, containsString("NO_BEARER_TOKEN"));
|
||||||
}
|
}
|
||||||
|
|
||||||
try (Response response = target.request().header(HttpHeaders.AUTHORIZATION, "Bearer null").get()) {
|
try (Response response = target.request().header(HttpHeaders.AUTHORIZATION, "Bearer null").get()) {
|
||||||
assertEquals(401, response.getStatus());
|
assertEquals(401, response.getStatus());
|
||||||
String errorPageResponse = response.readEntity(String.class);
|
String errorPageResponse = response.readEntity(String.class);
|
||||||
assertThat(errorPageResponse, containsString("Error Page"));
|
assertThat(errorPageResponse, containsString("Error Page"));
|
||||||
assertThat(errorPageResponse, containsString(OIDCAuthenticationError.Reason.INVALID_TOKEN.toString()));
|
assertThat(errorPageResponse, containsString("INVALID_TOKEN"));
|
||||||
}
|
}
|
||||||
|
|
||||||
client.close();
|
client.close();
|
||||||
|
|
|
@ -865,7 +865,7 @@ public class PolicyEnforcerTest extends AbstractKeycloakTest {
|
||||||
} catch (JWSInputException cause) {
|
} catch (JWSInputException cause) {
|
||||||
throw new RuntimeException(cause);
|
throw new RuntimeException(cause);
|
||||||
}
|
}
|
||||||
return new RefreshableKeycloakSecurityContext(deployment, null, token, accessToken, null, null, null);
|
return new RefreshableKeycloakSecurityContext(deployment, token, accessToken, null, null, null);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,5 +91,13 @@
|
||||||
<groupId>jakarta.servlet</groupId>
|
<groupId>jakarta.servlet</groupId>
|
||||||
<artifactId>jakarta.servlet-api</artifactId>
|
<artifactId>jakarta.servlet-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-adapter-spi</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-policy-enforcer</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -17,11 +17,11 @@
|
||||||
|
|
||||||
package org.keycloak.adapters;
|
package org.keycloak.adapters;
|
||||||
|
|
||||||
|
import org.keycloak.adapters.pep.HttpAuthzRequest;
|
||||||
|
import org.keycloak.adapters.pep.HttpAuthzResponse;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.AuthorizationContext;
|
import org.keycloak.AuthorizationContext;
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
import org.keycloak.KeycloakSecurityContext;
|
||||||
import org.keycloak.adapters.pep.HttpAuthzRequest;
|
|
||||||
import org.keycloak.adapters.pep.HttpAuthzResponse;
|
|
||||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||||
import org.keycloak.common.util.UriUtils;
|
import org.keycloak.common.util.UriUtils;
|
||||||
import org.keycloak.constants.AdapterConstants;
|
import org.keycloak.constants.AdapterConstants;
|
|
@ -17,13 +17,13 @@
|
||||||
|
|
||||||
package org.keycloak.adapters;
|
package org.keycloak.adapters;
|
||||||
|
|
||||||
|
import org.keycloak.adapters.rotation.PublicKeyLocator;
|
||||||
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.apache.http.client.methods.HttpGet;
|
||||||
import org.apache.http.util.EntityUtils;
|
import org.apache.http.util.EntityUtils;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||||
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.common.util.KeycloakUriBuilder;
|
|
@ -38,16 +38,14 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
|
||||||
protected static Logger log = Logger.getLogger(RefreshableKeycloakSecurityContext.class);
|
protected static Logger log = Logger.getLogger(RefreshableKeycloakSecurityContext.class);
|
||||||
|
|
||||||
protected transient KeycloakDeployment deployment;
|
protected transient KeycloakDeployment deployment;
|
||||||
protected transient AdapterTokenStore tokenStore;
|
|
||||||
protected String refreshToken;
|
protected String refreshToken;
|
||||||
|
|
||||||
public RefreshableKeycloakSecurityContext() {
|
public RefreshableKeycloakSecurityContext() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public RefreshableKeycloakSecurityContext(KeycloakDeployment deployment, AdapterTokenStore tokenStore, String tokenString, AccessToken token, String idTokenString, IDToken idToken, String refreshToken) {
|
public RefreshableKeycloakSecurityContext(KeycloakDeployment deployment, String tokenString, AccessToken token, String idTokenString, IDToken idToken, String refreshToken) {
|
||||||
super(tokenString, token, idTokenString, idToken);
|
super(tokenString, token, idTokenString, idToken);
|
||||||
this.deployment = deployment;
|
this.deployment = deployment;
|
||||||
this.tokenStore = tokenStore;
|
|
||||||
this.refreshToken = refreshToken;
|
this.refreshToken = refreshToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,9 +97,8 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
|
||||||
return deployment;
|
return deployment;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCurrentRequestInfo(KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
|
public void setCurrentRequestInfo(KeycloakDeployment deployment) {
|
||||||
this.deployment = deployment;
|
this.deployment = deployment;
|
||||||
this.tokenStore = tokenStore;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -182,9 +179,6 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
|
||||||
this.refreshToken = response.getRefreshToken();
|
this.refreshToken = response.getRefreshToken();
|
||||||
}
|
}
|
||||||
this.tokenString = tokenString;
|
this.tokenString = tokenString;
|
||||||
if (tokenStore != null) {
|
|
||||||
tokenStore.refreshCallback(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
|
@ -20,8 +20,8 @@ package org.keycloak.adapters.pep;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.OIDCHttpFacade;
|
import org.keycloak.adapters.OIDCHttpFacade;
|
||||||
|
import org.keycloak.KeycloakSecurityContext;
|
||||||
import org.keycloak.adapters.authorization.TokenPrincipal;
|
import org.keycloak.adapters.authorization.TokenPrincipal;
|
||||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||||
import org.keycloak.adapters.spi.HttpFacade.Cookie;
|
import org.keycloak.adapters.spi.HttpFacade.Cookie;
|
|
@ -17,9 +17,9 @@
|
||||||
|
|
||||||
package org.keycloak.adapters.rotation;
|
package org.keycloak.adapters.rotation;
|
||||||
|
|
||||||
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.TokenVerifier;
|
import org.keycloak.TokenVerifier;
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.common.VerificationException;
|
import org.keycloak.common.VerificationException;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.representations.IDToken;
|
import org.keycloak.representations.IDToken;
|
Loading…
Reference in a new issue