KEYCLOAK-7635 : Authenticate clients with x509 certificate
This commit is contained in:
parent
575851d45c
commit
02b2a8aab0
12 changed files with 392 additions and 33 deletions
|
@ -17,6 +17,7 @@ env:
|
|||
- TESTS=old
|
||||
- TESTS=crossdc-server
|
||||
- TESTS=crossdc-adapter
|
||||
- TESTS=ssl
|
||||
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
|
|
|
@ -394,6 +394,14 @@ public class DefaultAuthenticationFlows {
|
|||
execution.setAuthenticatorFlow(false);
|
||||
realm.addAuthenticatorExecution(execution);
|
||||
|
||||
execution = new AuthenticationExecutionModel();
|
||||
execution.setParentFlow(clients.getId());
|
||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
|
||||
execution.setAuthenticator("client-x509");
|
||||
execution.setPriority(40);
|
||||
execution.setAuthenticatorFlow(false);
|
||||
realm.addAuthenticatorExecution(execution);
|
||||
|
||||
}
|
||||
|
||||
public static void firstBrokerLoginFlow(RealmModel realm, boolean migrate) {
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
package org.keycloak.authentication.authenticators.client;
|
||||
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.authentication.AuthenticationFlowError;
|
||||
import org.keycloak.authentication.ClientAuthenticationFlowContext;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.services.ServicesLogger;
|
||||
import org.keycloak.services.x509.X509ClientCertificateLookup;
|
||||
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.*;
|
||||
|
||||
public class X509ClientAuthenticator extends AbstractClientAuthenticator {
|
||||
|
||||
public static final String PROVIDER_ID = "client-x509";
|
||||
protected static ServicesLogger logger = ServicesLogger.LOGGER;
|
||||
|
||||
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
|
||||
AuthenticationExecutionModel.Requirement.ALTERNATIVE,
|
||||
AuthenticationExecutionModel.Requirement.DISABLED
|
||||
};
|
||||
|
||||
@Override
|
||||
public void authenticateClient(ClientAuthenticationFlowContext context) {
|
||||
|
||||
X509ClientCertificateLookup provider = context.getSession().getProvider(X509ClientCertificateLookup.class);
|
||||
if (provider == null) {
|
||||
logger.errorv("\"{0}\" Spi is not available, did you forget to update the configuration?",
|
||||
X509ClientCertificateLookup.class);
|
||||
return;
|
||||
}
|
||||
|
||||
X509Certificate[] certs = new X509Certificate[0];
|
||||
try {
|
||||
certs = provider.getCertificateChain(context.getHttpRequest());
|
||||
String client_id = null;
|
||||
MediaType mediaType = context.getHttpRequest().getHttpHeaders().getMediaType();
|
||||
boolean hasFormData = mediaType != null && mediaType.isCompatible(MediaType.APPLICATION_FORM_URLENCODED_TYPE);
|
||||
|
||||
MultivaluedMap<String, String> formData = hasFormData ? context.getHttpRequest().getDecodedFormParameters() : null;
|
||||
MultivaluedMap<String, String> queryParams = context.getHttpRequest().getUri().getQueryParameters();
|
||||
|
||||
if (formData != null) {
|
||||
client_id = formData.getFirst(OAuth2Constants.CLIENT_ID);
|
||||
}
|
||||
|
||||
if (client_id == null) {
|
||||
if (queryParams != null) {
|
||||
client_id = queryParams.getFirst(OAuth2Constants.CLIENT_ID);
|
||||
} else {
|
||||
Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_client", "Missing client_id parameter");
|
||||
context.challenge(challengeResponse);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ClientModel client = context.getRealm().getClientByClientId(client_id);
|
||||
if (client == null) {
|
||||
context.failure(AuthenticationFlowError.CLIENT_NOT_FOUND, null);
|
||||
return;
|
||||
}
|
||||
context.getEvent().client(client_id);
|
||||
context.setClient(client);
|
||||
|
||||
if (!client.isEnabled()) {
|
||||
context.failure(AuthenticationFlowError.CLIENT_DISABLED, null);
|
||||
return;
|
||||
}
|
||||
} catch (GeneralSecurityException e) {
|
||||
logger.errorf("[X509ClientCertificateAuthenticator:authenticate] Exception: %s", e.getMessage());
|
||||
context.attempted();
|
||||
}
|
||||
|
||||
if (certs == null || certs.length == 0) {
|
||||
// No x509 client cert, fall through and
|
||||
// continue processing the rest of the authentication flow
|
||||
logger.debug("[X509ClientCertificateAuthenticator:authenticate] x509 client certificate is not available for mutual SSL.");
|
||||
context.attempted();
|
||||
return;
|
||||
}
|
||||
|
||||
context.success();
|
||||
}
|
||||
|
||||
public String getDisplayType() {
|
||||
return "X509 Certificate";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigurable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||
return REQUIREMENT_CHOICES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigPropertiesPerClient() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAdapterConfiguration(ClientModel client) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getProtocolAuthenticatorMethods(String loginProtocol) {
|
||||
if (loginProtocol.equals(OIDCLoginProtocol.LOGIN_PROTOCOL)) {
|
||||
Set<String> results = new HashSet<>();
|
||||
return results;
|
||||
} else {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "Validates client based on a X509 Certificate";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
}
|
|
@ -17,4 +17,5 @@
|
|||
|
||||
org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator
|
||||
org.keycloak.authentication.authenticators.client.JWTClientAuthenticator
|
||||
org.keycloak.authentication.authenticators.client.JWTClientSecretAuthenticator
|
||||
org.keycloak.authentication.authenticators.client.JWTClientSecretAuthenticator
|
||||
org.keycloak.authentication.authenticators.client.X509ClientAuthenticator
|
|
@ -463,6 +463,17 @@ To run the Mutual TLS Client Certificate Bound Access Tokens tests:
|
|||
-Dbrowser=phantomjs \
|
||||
-Dtest=org.keycloak.testsuite.hok.HoKTest
|
||||
|
||||
## Run Mutual TLS for the Client tests
|
||||
|
||||
To run the Mutual TLS test for the client:
|
||||
|
||||
mvn -f testsuite/integration-arquillian/pom.xml \
|
||||
clean install \
|
||||
-Pauth-server-wildfly \
|
||||
-Dauth.server.ssl.required \
|
||||
-Dbrowser=phantomjs \
|
||||
-Dtest=org.keycloak.testsuite.client.MutualTLSClientTest
|
||||
|
||||
## Cluster tests
|
||||
|
||||
Cluster tests use 2 backend servers (Keycloak on Wildfly/EAP) and 1 frontend loadbalancer server node. Invalidation tests don't use loadbalancer.
|
||||
|
|
|
@ -18,9 +18,13 @@ import org.apache.http.impl.client.HttpClientBuilder;
|
|||
import org.keycloak.common.util.Base64Url;
|
||||
import org.keycloak.common.util.KeystoreUtil;
|
||||
|
||||
public class HoKTokenUtils {
|
||||
// KEYCLOAK-6771 Certificate Bound Token
|
||||
// https://tools.ietf.org/html/draft-ietf-oauth-mtls-08#section-3
|
||||
/**
|
||||
* Utilities for Holder of key mechanism and other Mutual TLS tests.
|
||||
*
|
||||
* @see https://issues.jboss.org/browse/KEYCLOAK-6771
|
||||
* @see https://tools.ietf.org/html/draft-ietf-oauth-mtls-08#section-3
|
||||
*/
|
||||
public class MutualTLSUtils {
|
||||
|
||||
public static final String DEFAULT_KEYSTOREPATH = System.getProperty("client.certificate.keystore");
|
||||
public static final String DEFAULT_KEYSTOREPASSWORD = System.getProperty("client.certificate.keystore.passphrase");
|
|
@ -21,6 +21,7 @@ import org.apache.commons.io.IOUtils;
|
|||
import org.apache.commons.io.output.ByteArrayOutputStream;
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
|
@ -76,6 +77,8 @@ import java.util.HashMap;
|
|||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.core.Form;
|
||||
|
||||
|
@ -145,6 +148,8 @@ public class OAuthClient {
|
|||
private String codeChallengeMethod;
|
||||
private String origin;
|
||||
|
||||
private Supplier<CloseableHttpClient> httpClient = OAuthClient::newCloseableHttpClient;
|
||||
|
||||
public class LogoutUrlBuilder {
|
||||
private final UriBuilder b = OIDCLoginProtocolService.logoutUrl(UriBuilder.fromUri(baseUrl));
|
||||
|
||||
|
@ -243,7 +248,12 @@ public class OAuthClient {
|
|||
fillLoginForm(username, password);
|
||||
}
|
||||
|
||||
private static CloseableHttpClient newCloseableHttpClient() {
|
||||
public OAuthClient httpClient(Supplier<CloseableHttpClient> client) {
|
||||
this.httpClient = client;
|
||||
return this;
|
||||
}
|
||||
|
||||
public static CloseableHttpClient newCloseableHttpClient() {
|
||||
if (sslRequired) {
|
||||
KeyStore keystore = null;
|
||||
// load the keystore containing the client certificate - keystore type is probably jks or pkcs12
|
||||
|
@ -274,7 +284,7 @@ public class OAuthClient {
|
|||
}
|
||||
|
||||
public CloseableHttpResponse doPreflightRequest() {
|
||||
try (CloseableHttpClient client = newCloseableHttpClient()) {
|
||||
try (CloseableHttpClient client = httpClient.get()) {
|
||||
HttpOptions options = new HttpOptions(getAccessTokenUrl());
|
||||
options.setHeader("Origin", "http://example.com");
|
||||
|
||||
|
@ -286,7 +296,7 @@ public class OAuthClient {
|
|||
|
||||
// KEYCLOAK-6771 Certificate Bound Token
|
||||
public AccessTokenResponse doAccessTokenRequest(String code, String password) {
|
||||
try (CloseableHttpClient client = newCloseableHttpClient()) {
|
||||
try (CloseableHttpClient client = httpClient.get()) {
|
||||
return doAccessTokenRequest(code, password, client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
|
@ -398,7 +408,7 @@ public class OAuthClient {
|
|||
|
||||
public AccessTokenResponse doGrantAccessTokenRequest(String realm, String username, String password, String totp,
|
||||
String clientId, String clientSecret) throws Exception {
|
||||
try (CloseableHttpClient client = newCloseableHttpClient()) {
|
||||
try (CloseableHttpClient client = httpClient.get()) {
|
||||
HttpPost post = new HttpPost(getResourceOwnerPasswordCredentialGrantUrl(realm));
|
||||
|
||||
List<NameValuePair> parameters = new LinkedList<>();
|
||||
|
@ -444,7 +454,7 @@ public class OAuthClient {
|
|||
|
||||
public AccessTokenResponse doTokenExchange(String realm, String token, String targetAudience,
|
||||
String clientId, String clientSecret) throws Exception {
|
||||
try (CloseableHttpClient client = newCloseableHttpClient()) {
|
||||
try (CloseableHttpClient client = httpClient.get()) {
|
||||
HttpPost post = new HttpPost(getResourceOwnerPasswordCredentialGrantUrl(realm));
|
||||
|
||||
List<NameValuePair> parameters = new LinkedList<>();
|
||||
|
@ -484,7 +494,7 @@ public class OAuthClient {
|
|||
}
|
||||
|
||||
public AccessTokenResponse doTokenExchange(String realm, String clientId, String clientSecret, Map<String, String> params) throws Exception {
|
||||
try (CloseableHttpClient client = newCloseableHttpClient()) {
|
||||
try (CloseableHttpClient client = httpClient.get()) {
|
||||
HttpPost post = new HttpPost(getResourceOwnerPasswordCredentialGrantUrl(realm));
|
||||
|
||||
List<NameValuePair> parameters = new LinkedList<>();
|
||||
|
|
|
@ -141,11 +141,13 @@ public class InitialFlowsTest extends AbstractAuthenticationTest {
|
|||
addExecExport(flow, null, false, "client-secret", false, null, ALTERNATIVE, 10);
|
||||
addExecExport(flow, null, false, "client-jwt", false, null, ALTERNATIVE, 20);
|
||||
addExecExport(flow, null, false, "client-secret-jwt", false, null, ALTERNATIVE, 30);
|
||||
addExecExport(flow, null, false, "client-x509", false, null, ALTERNATIVE, 40);
|
||||
|
||||
execs = new LinkedList<>();
|
||||
addExecInfo(execs, "Client Id and Secret", "client-secret", false, 0, 0, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED});
|
||||
addExecInfo(execs, "Signed Jwt", "client-jwt", false, 0, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED});
|
||||
addExecInfo(execs, "Signed Jwt with Client Secret", "client-secret-jwt", false, 0, 2, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED});
|
||||
addExecInfo(execs, "X509 Certificate", "client-x509", false, 0, 3, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED});
|
||||
expected.add(new FlowExecutions(flow, execs));
|
||||
|
||||
flow = newFlow("direct grant", "OpenID Connect Resource Owner Grant", "basic-flow", true, true);
|
||||
|
|
|
@ -81,9 +81,12 @@ public class ProvidersTest extends AbstractAuthenticationTest {
|
|||
"'client_secret' sent either in request parameters or in 'Authorization: Basic' header");
|
||||
addProviderInfo(expected, "testsuite-client-passthrough", "Testsuite Dummy Client Validation", "Testsuite dummy authenticator, " +
|
||||
"which automatically authenticates hardcoded client (like 'test-app' )");
|
||||
addProviderInfo(expected, "client-x509", "X509 Certificate",
|
||||
"Validates client based on a X509 Certificate");
|
||||
addProviderInfo(expected, "client-secret-jwt", "Signed Jwt with Client Secret",
|
||||
"Validates client based on signed JWT issued by client and signed with the Client Secret");
|
||||
|
||||
|
||||
compareProviders(expected, result);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
package org.keycloak.testsuite.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Assume;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.authentication.authenticators.client.X509ClientAuthenticator;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||
import org.keycloak.testsuite.util.KeycloakModelUtils;
|
||||
import org.keycloak.testsuite.util.MutualTLSUtils;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
/**
|
||||
* Mutual TLS Client tests.
|
||||
*/
|
||||
public class MutualTLSClientTest extends AbstractTestRealmKeycloakTest {
|
||||
|
||||
private static final boolean sslRequired = Boolean.parseBoolean(System.getProperty("auth.server.ssl.required"));
|
||||
|
||||
private static final String CLIENT_ID = "confidential-x509";
|
||||
private static final String DISABLED_CLIENT_ID = "confidential-disabled-x509";
|
||||
private static final String USER = "keycloak-user@localhost";
|
||||
private static final String PASSWORD = "password";
|
||||
private static final String REALM = "test";
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
ClientRepresentation properConfiguration = KeycloakModelUtils.createClient(testRealm, CLIENT_ID);
|
||||
properConfiguration.setServiceAccountsEnabled(Boolean.TRUE);
|
||||
properConfiguration.setRedirectUris(Arrays.asList("https://localhost:8543/auth/realms/master/app/auth"));
|
||||
properConfiguration.setClientAuthenticatorType(X509ClientAuthenticator.PROVIDER_ID);
|
||||
|
||||
ClientRepresentation disabledConfiguration = KeycloakModelUtils.createClient(testRealm, DISABLED_CLIENT_ID);
|
||||
disabledConfiguration.setServiceAccountsEnabled(Boolean.TRUE);
|
||||
disabledConfiguration.setRedirectUris(Arrays.asList("https://localhost:8543/auth/realms/master/app/auth"));
|
||||
disabledConfiguration.setClientAuthenticatorType(X509ClientAuthenticator.PROVIDER_ID);
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void sslRequired() {
|
||||
Assume.assumeTrue("\"auth.server.ssl.required\" is required for Mutual TLS tests", sslRequired);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccessfulClientInvocationWithProperCertificate() throws Exception {
|
||||
//given
|
||||
Supplier<CloseableHttpClient> clientWithProperCertificate = MutualTLSUtils::newCloseableHttpClientWithDefaultKeyStoreAndTrustStore;
|
||||
|
||||
//when
|
||||
OAuthClient.AccessTokenResponse token = loginAndGetAccessTokenResponse(CLIENT_ID, clientWithProperCertificate);
|
||||
|
||||
//then
|
||||
assertTokenObtained(token);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccessfulClientInvocationWithClientIdInQueryParams() throws Exception {
|
||||
//given//when
|
||||
OAuthClient.AccessTokenResponse token = null;
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
|
||||
login(CLIENT_ID);
|
||||
token = getAccessTokenResponseWithQueryParams(CLIENT_ID, client);
|
||||
}
|
||||
|
||||
//then
|
||||
assertTokenObtained(token);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailedClientInvocationWithoutCertificateCertificate() throws Exception {
|
||||
//given
|
||||
Supplier<CloseableHttpClient> clientWithoutCertificate = MutualTLSUtils::newCloseableHttpClientWithoutKeyStoreAndTrustStore;
|
||||
|
||||
//when
|
||||
OAuthClient.AccessTokenResponse token = loginAndGetAccessTokenResponse(CLIENT_ID, clientWithoutCertificate);
|
||||
|
||||
//then
|
||||
assertTokenNotObtained(token);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailedClientInvocationWithDisabledClient() throws Exception {
|
||||
//given//when
|
||||
OAuthClient.AccessTokenResponse token = null;
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
|
||||
login(DISABLED_CLIENT_ID);
|
||||
|
||||
disableClient(DISABLED_CLIENT_ID);
|
||||
|
||||
token = getAccessTokenResponse(DISABLED_CLIENT_ID, client);
|
||||
}
|
||||
|
||||
//then
|
||||
assertTokenNotObtained(token);
|
||||
}
|
||||
|
||||
private OAuthClient.AccessTokenResponse loginAndGetAccessTokenResponse(String clientId, Supplier<CloseableHttpClient> client) throws IOException{
|
||||
try (CloseableHttpClient closeableHttpClient = client.get()) {
|
||||
login(clientId);
|
||||
return getAccessTokenResponse(clientId, closeableHttpClient);
|
||||
} catch (IOException ioe) {
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
|
||||
private OAuthClient.AccessTokenResponse getAccessTokenResponse(String clientId, CloseableHttpClient closeableHttpClient) {
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
// Call protected endpoint with supplied client.
|
||||
return oauth
|
||||
.httpClient(() -> closeableHttpClient)
|
||||
.clientId(clientId)
|
||||
.doAccessTokenRequest(code, null, closeableHttpClient);
|
||||
}
|
||||
|
||||
private void login(String clientId) {
|
||||
// Login with default client, despite what has been supplied into this method.
|
||||
oauth
|
||||
.httpClient(OAuthClient::newCloseableHttpClient)
|
||||
.clientId(clientId)
|
||||
.doLogin(USER, PASSWORD);
|
||||
}
|
||||
|
||||
private void assertTokenObtained(OAuthClient.AccessTokenResponse token) {
|
||||
Assert.assertEquals(200, token.getStatusCode());
|
||||
Assert.assertNotNull(token.getAccessToken());
|
||||
}
|
||||
|
||||
private void assertTokenNotObtained(OAuthClient.AccessTokenResponse token) {
|
||||
Assert.assertEquals(400, token.getStatusCode());
|
||||
Assert.assertNull(token.getAccessToken());
|
||||
}
|
||||
|
||||
/*
|
||||
* This is a very simplified version of OAuthClient#doAccessTokenRequest.
|
||||
* It test a scenario, where we do not follow the spec and specify client_id in Query Params (for in a form).
|
||||
*/
|
||||
private OAuthClient.AccessTokenResponse getAccessTokenResponseWithQueryParams(String clientId, CloseableHttpClient client) throws Exception {
|
||||
OAuthClient.AccessTokenResponse token;// This is a very simplified version of
|
||||
HttpPost post = new HttpPost(oauth.getAccessTokenUrl() + "?client_id=" + clientId);
|
||||
List<NameValuePair> parameters = new LinkedList<>();
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.AUTHORIZATION_CODE));
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.CODE, oauth.getCurrentQuery().get(OAuth2Constants.CODE)));
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.REDIRECT_URI, oauth.getRedirectUri()));
|
||||
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters, Charsets.UTF_8);
|
||||
post.setEntity(formEntity);
|
||||
|
||||
return new OAuthClient.AccessTokenResponse(client.execute(post));
|
||||
}
|
||||
|
||||
private void disableClient(String clientId) {
|
||||
ClientRepresentation disabledClientRepresentation = adminClient.realm(REALM).clients().findByClientId(clientId).get(0);
|
||||
ClientResource disabledClientResource = adminClient.realms().realm(REALM).clients().get(disabledClientRepresentation.getId());
|
||||
disabledClientRepresentation.setEnabled(false);
|
||||
disabledClientResource.update(disabledClientRepresentation);
|
||||
}
|
||||
}
|
|
@ -61,7 +61,7 @@ import org.keycloak.testsuite.AssertEvents;
|
|||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.drone.Different;
|
||||
import org.keycloak.testsuite.util.ClientManager;
|
||||
import org.keycloak.testsuite.util.HoKTokenUtils;
|
||||
import org.keycloak.testsuite.util.MutualTLSUtils;
|
||||
import org.keycloak.testsuite.util.KeycloakModelUtils;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.testsuite.util.UserInfoClientUtil;
|
||||
|
@ -174,7 +174,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
|
|||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
|
||||
AccessTokenResponse response;
|
||||
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
|
||||
response = oauth.doAccessTokenRequest(code, "password", client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
|
@ -191,7 +191,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
|
|||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
|
||||
AccessTokenResponse response;
|
||||
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) {
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) {
|
||||
response = oauth.doAccessTokenRequest(code, "password", client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
|
@ -238,7 +238,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
|
|||
|
||||
assertEquals(sessionId, token.getSessionState());
|
||||
|
||||
//assertEquals(1, token.getRealmAccess().getRoles().size());
|
||||
assertEquals(2, token.getRealmAccess().getRoles().size());
|
||||
assertTrue(token.getRealmAccess().isUserInRole("user"));
|
||||
|
||||
assertEquals(1, token.getResourceAccess(oauth.getClientId()).getRoles().size());
|
||||
|
@ -258,7 +258,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
|
|||
oauth.doLogin("test-user@localhost", "password");
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
AccessTokenResponse tokenResponse = null;
|
||||
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
|
||||
tokenResponse = oauth.doAccessTokenRequest(code, "password", client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
|
@ -272,7 +272,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
|
|||
oauth2.doLogin("john-doh@localhost", "password");
|
||||
String code2 = oauth2.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
AccessTokenResponse tokenResponse2 = null;
|
||||
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithOtherKeyStoreAndTrustStore()) {
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithOtherKeyStoreAndTrustStore()) {
|
||||
tokenResponse2 = oauth2.doAccessTokenRequest(code2, "password", client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
|
@ -281,7 +281,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
|
|||
|
||||
// token refresh by second client by first client's refresh token
|
||||
AccessTokenResponse response = null;
|
||||
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithOtherKeyStoreAndTrustStore()) {
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithOtherKeyStoreAndTrustStore()) {
|
||||
response = oauth2.doRefreshTokenRequest(refreshTokenString, "password", client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
|
@ -303,7 +303,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
|
|||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
|
||||
AccessTokenResponse tokenResponse = null;
|
||||
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
|
||||
tokenResponse = oauth.doAccessTokenRequest(code, "password", client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
|
@ -325,7 +325,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
|
|||
setTimeOffset(2);
|
||||
|
||||
AccessTokenResponse response = null;
|
||||
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
|
||||
response = oauth.doRefreshTokenRequest(refreshTokenString, "password", client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
|
@ -362,7 +362,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
|
|||
setTimeOffset(2);
|
||||
|
||||
AccessTokenResponse response = null;
|
||||
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) {
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) {
|
||||
response = oauth.doRefreshTokenRequest(refreshTokenString, "password", client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
|
@ -405,7 +405,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
|
|||
assertEquals(findUserByUsername(adminClient.realm("test"), username).getId(), refreshedToken.getSubject());
|
||||
Assert.assertNotEquals(username, refreshedToken.getSubject());
|
||||
|
||||
//assertEquals(1, refreshedToken.getRealmAccess().getRoles().size());
|
||||
assertEquals(2, refreshedToken.getRealmAccess().getRoles().size());
|
||||
Assert.assertTrue(refreshedToken.getRealmAccess().isUserInRole("user"));
|
||||
|
||||
assertEquals(1, refreshedToken.getResourceAccess(oauth.getClientId()).getRoles().size());
|
||||
|
@ -431,7 +431,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
|
|||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
|
||||
AccessTokenResponse tokenResponse = null;
|
||||
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
|
||||
tokenResponse = oauth.doAccessTokenRequest(code, "password", client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
|
@ -442,8 +442,8 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
|
|||
// execute the access token to get UserInfo with token binded client certificate in mutual authentication TLS
|
||||
ClientBuilder clientBuilder = ClientBuilder.newBuilder();
|
||||
KeyStore keystore = null;
|
||||
keystore = KeystoreUtil.loadKeyStore(HoKTokenUtils.DEFAULT_KEYSTOREPATH, HoKTokenUtils.DEFAULT_KEYSTOREPASSWORD);
|
||||
clientBuilder.keyStore(keystore, HoKTokenUtils.DEFAULT_KEYSTOREPASSWORD);
|
||||
keystore = KeystoreUtil.loadKeyStore(MutualTLSUtils.DEFAULT_KEYSTOREPATH, MutualTLSUtils.DEFAULT_KEYSTOREPASSWORD);
|
||||
clientBuilder.keyStore(keystore, MutualTLSUtils.DEFAULT_KEYSTOREPASSWORD);
|
||||
Client client = clientBuilder.build();
|
||||
WebTarget userInfoTarget = null;
|
||||
Response response = null;
|
||||
|
@ -469,7 +469,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
|
|||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
|
||||
AccessTokenResponse tokenResponse = null;
|
||||
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
|
||||
tokenResponse = oauth.doAccessTokenRequest(code, "password", client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
|
@ -510,7 +510,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
|
|||
String refreshTokenString = execPreProcessPostLogout();
|
||||
|
||||
CloseableHttpResponse response = null;
|
||||
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
|
||||
response = oauth.doLogout(refreshTokenString, "password", client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
|
@ -526,7 +526,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
|
|||
String refreshTokenString = execPreProcessPostLogout();
|
||||
|
||||
CloseableHttpResponse response = null;
|
||||
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) {
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) {
|
||||
response = oauth.doLogout(refreshTokenString, "password", client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
|
@ -596,7 +596,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
|
|||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
EventRepresentation loginEvent = events.expectLogin().assertEvent();
|
||||
AccessTokenResponse accessTokenResponse = null;
|
||||
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
|
||||
accessTokenResponse = oauth.doAccessTokenRequest(code, "password", client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
|
@ -605,7 +605,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
|
|||
// Do token introspection
|
||||
// mimic Resource Server
|
||||
String tokenResponse;
|
||||
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) {
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) {
|
||||
tokenResponse = oauth.introspectTokenWithClientCredential("confidential-cli", "secret1", "access_token", accessTokenResponse.getAccessToken(), client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
|
@ -618,7 +618,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
|
|||
String certThumprintFromAccessToken = at.getCertConf().getCertThumbprint();
|
||||
String certThumprintFromRefreshToken = rt.getCertConf().getCertThumbprint();
|
||||
String certThumprintFromTokenIntrospection = rep.getCertConf().getCertThumbprint();
|
||||
String certThumprintFromBoundClientCertificate = HoKTokenUtils.getThumbprintFromDefaultClientCert();
|
||||
String certThumprintFromBoundClientCertificate = MutualTLSUtils.getThumbprintFromDefaultClientCert();
|
||||
|
||||
assertTrue(rep.isActive());
|
||||
assertEquals("test-user@localhost", rep.getUserName());
|
||||
|
@ -633,11 +633,11 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
|
|||
|
||||
|
||||
private void verifyHoKTokenDefaultCertThumbPrint(AccessTokenResponse response) throws Exception {
|
||||
verifyHoKTokenCertThumbPrint(response, HoKTokenUtils.getThumbprintFromDefaultClientCert());
|
||||
verifyHoKTokenCertThumbPrint(response, MutualTLSUtils.getThumbprintFromDefaultClientCert());
|
||||
}
|
||||
|
||||
private void verifyHoKTokenOtherCertThumbPrint(AccessTokenResponse response) throws Exception {
|
||||
verifyHoKTokenCertThumbPrint(response, HoKTokenUtils.getThumbprintFromOtherClientCert());
|
||||
verifyHoKTokenCertThumbPrint(response, MutualTLSUtils.getThumbprintFromOtherClientCert());
|
||||
}
|
||||
|
||||
private void verifyHoKTokenCertThumbPrint(AccessTokenResponse response, String certThumbPrint) {
|
||||
|
|
|
@ -5,7 +5,7 @@ function run-server-tests() {
|
|||
mvn install -B -nsu -Pauth-server-wildfly -DskipTests
|
||||
|
||||
cd tests/base
|
||||
mvn test -B -nsu -Pauth-server-wildfly -Dtest=$1 2>&1 | java -cp ../../../utils/target/classes org.keycloak.testsuite.LogTrimmer
|
||||
mvn test -B -nsu -Pauth-server-wildfly -Dtest=$1 $2 2>&1 | java -cp ../../../utils/target/classes org.keycloak.testsuite.LogTrimmer
|
||||
exit ${PIPESTATUS[0]}
|
||||
}
|
||||
|
||||
|
@ -98,4 +98,8 @@ if [ $1 == "crossdc-adapter" ]; then
|
|||
mvn clean test -B -nsu -Pcache-server-infinispan,auth-servers-crossdc-jboss,auth-server-wildfly,app-server-wildfly -Dtest=org.keycloak.testsuite.adapter.**.crossdc.**.* 2>&1 |
|
||||
java -cp ../../../utils/target/classes org.keycloak.testsuite.LogTrimmer
|
||||
exit ${PIPESTATUS[0]}
|
||||
fi
|
||||
|
||||
if [ $1 == "ssl" ]; then
|
||||
run-server-tests org.keycloak.testsuite.client.MutualTLSClientTest,org.keycloak.testsuite.hok.HoKTest "-Dauth.server.ssl.required -Dbrowser=phantomjs"
|
||||
fi
|
Loading…
Reference in a new issue