KEYCLOAK-1295 Adapter support. Fixes

This commit is contained in:
mposolda 2015-08-18 16:56:41 +02:00
parent 87f7ec5909
commit d8d6348f67
64 changed files with 915 additions and 661 deletions

View file

@ -16,7 +16,7 @@ public class AbstractOAuthClient {
private final AtomicLong counter = new AtomicLong();
protected String clientId;
protected Map<String, String> credentials;
protected Map<String, Object> credentials;
protected String authUrl;
protected String tokenUrl;
protected RelativeUrlsUsed relativeUrlsUsed;
@ -37,11 +37,11 @@ public class AbstractOAuthClient {
this.clientId = clientId;
}
public Map<String, String> getCredentials() {
public Map<String, Object> getCredentials() {
return credentials;
}
public void setCredentials(Map<String, String> credentials) {
public void setCredentials(Map<String, Object> credentials) {
this.credentials = credentials;
}

View file

@ -39,7 +39,7 @@ public class BaseAdapterConfig extends BaseRealmConfig {
@JsonProperty("public-client")
protected boolean publicClient;
@JsonProperty("credentials")
protected Map<String, String> credentials = new HashMap<String, String>();
protected Map<String, Object> credentials = new HashMap<>();
public boolean isUseResourceRoleMappings() {
@ -114,11 +114,11 @@ public class BaseAdapterConfig extends BaseRealmConfig {
this.enableBasicAuth = enableBasicAuth;
}
public Map<String, String> getCredentials() {
public Map<String, Object> getCredentials() {
return credentials;
}
public void setCredentials(Map<String, String> credentials) {
public void setCredentials(Map<String, Object> credentials) {
this.credentials = credentials;
}

View file

@ -0,0 +1,19 @@
package org.keycloak.example;
/**
* Client authentication with traditional OAuth2 client_id + client_secret
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ProductSAClientSecretServlet extends ProductServiceAccountServlet {
@Override
protected String getAdapterConfigLocation() {
return "WEB-INF/keycloak-client-secret.json";
}
@Override
protected String getClientAuthenticationMethod() {
return "secret";
}
}

View file

@ -0,0 +1,17 @@
package org.keycloak.example;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ProductSAClientSignedJWTServlet extends ProductServiceAccountServlet {
@Override
protected String getAdapterConfigLocation() {
return "WEB-INF/keycloak-client-signed-jwt.json";
}
@Override
protected String getClientAuthenticationMethod() {
return "jwt";
}
}

View file

@ -4,7 +4,9 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
@ -26,30 +28,46 @@ import org.keycloak.VerificationException;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.ServerRequest;
import org.keycloak.constants.ServiceAccountConstants;
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.util.BasicAuthHelper;
import org.keycloak.util.JsonSerialization;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ProductServiceAccountServlet extends HttpServlet {
public abstract class ProductServiceAccountServlet extends HttpServlet {
public static final String ERROR = "error";
public static final String TOKEN = "token";
public static final String TOKEN_PARSED = "idTokenParsed";
public static final String REFRESH_TOKEN = "refreshToken";
public static final String PRODUCTS = "products";
public static final String CLIENT_AUTH_METHOD = "clientAuthMethod";
protected abstract String getAdapterConfigLocation();
protected abstract String getClientAuthenticationMethod();
public static String getLoginUrl(HttpServletRequest request) {
return "/service-account-portal/app-" + request.getAttribute(CLIENT_AUTH_METHOD) + "/login";
}
public static String getRefreshUrl(HttpServletRequest request) {
return "/service-account-portal/app-" + request.getAttribute(CLIENT_AUTH_METHOD) + "/refresh";
}
public static String getLogoutUrl(HttpServletRequest request) {
return "/service-account-portal/app-" + request.getAttribute(CLIENT_AUTH_METHOD) + "/logout";
}
@Override
public void init() throws ServletException {
InputStream config = getServletContext().getResourceAsStream("WEB-INF/keycloak.json");
String adapterConfigLocation = getAdapterConfigLocation();
InputStream config = getServletContext().getResourceAsStream(adapterConfigLocation);
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(config);
HttpClient client = new DefaultHttpClient();
getServletContext().setAttribute("deployment-" + getClientAuthenticationMethod(), deployment);
getServletContext().setAttribute(KeycloakDeployment.class.getName(), deployment);
HttpClient client = new DefaultHttpClient();
getServletContext().setAttribute(HttpClient.class.getName(), client);
}
@ -60,6 +78,8 @@ public class ProductServiceAccountServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setAttribute(CLIENT_AUTH_METHOD, getClientAuthenticationMethod());
String reqUri = req.getRequestURI();
if (reqUri.endsWith("/login")) {
serviceAccountLogin(req);
@ -81,16 +101,21 @@ public class ProductServiceAccountServlet extends HttpServlet {
KeycloakDeployment deployment = getKeycloakDeployment();
HttpClient client = getHttpClient();
String clientId = deployment.getResourceName();
String clientSecret = deployment.getResourceCredentials().get("secret");
try {
HttpPost post = new HttpPost(deployment.getTokenUrl());
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS));
String authHeader = BasicAuthHelper.createHeader(clientId, clientSecret);
post.addHeader("Authorization", authHeader);
// Add client credentials according to the method configured in keycloak.json file
Map<String, String> reqHeaders = new HashMap<>();
Map<String, String> reqParams = new HashMap<>();
ClientCredentialsProviderUtils.setClientCredentials(deployment, reqHeaders, reqParams);
for (Map.Entry<String, String> header : reqHeaders.entrySet()) {
post.setHeader(header.getKey(), header.getValue());
}
for (Map.Entry<String, String> param : reqParams.entrySet()) {
formparams.add(new BasicNameValuePair(param.getKey(), param.getValue()));
}
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
post.setEntity(form);
@ -217,7 +242,7 @@ public class ProductServiceAccountServlet extends HttpServlet {
}
private KeycloakDeployment getKeycloakDeployment() {
return (KeycloakDeployment) getServletContext().getAttribute(KeycloakDeployment.class.getName());
return (KeycloakDeployment) getServletContext().getAttribute("deployment-" + getClientAuthenticationMethod());
}
private HttpClient getHttpClient() {

View file

@ -0,0 +1,17 @@
{
"realm" : "demo",
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "http://localhost:8080/auth",
"ssl-required" : "external",
"resource" : "product-sa-client",
"credentials": {
"jwt": {
"client-keystore-file": "classpath:keystore-client.jks",
"client-keystore-type": "JKS",
"client-keystore-password": "storepass",
"client-key-password": "keypass",
"client-key-alias": "clientkey",
"token-expiration": 10
}
}
}

View file

@ -13,10 +13,16 @@
AccessToken token = (AccessToken) request.getSession().getAttribute(ProductServiceAccountServlet.TOKEN_PARSED);
String products = (String) request.getAttribute(ProductServiceAccountServlet.PRODUCTS);
String appError = (String) request.getAttribute(ProductServiceAccountServlet.ERROR);
String clientAuthMethod = (String) request.getAttribute(ProductServiceAccountServlet.CLIENT_AUTH_METHOD);
String loginUrl = ProductServiceAccountServlet.getLoginUrl(request);
String refreshUrl = ProductServiceAccountServlet.getRefreshUrl(request);
String logoutUrl = ProductServiceAccountServlet.getLogoutUrl(request);
%>
<h1>Service account portal</h1>
<p><a href="/service-account-portal/app/login">Login</a> | <a href="/service-account-portal/app/refresh">Refresh token</a> | <a
href="/service-account-portal/app/logout">Logout</a></p>
<h2>Client authentication method: <%= clientAuthMethod %></h2>
<p><a href="<%= loginUrl %>">Login</a> | <a href="<%= refreshUrl %>">Refresh token</a> | <a
href="<%= logoutUrl %>">Logout</a></p>
<hr />
<% if (appError != null) { %>

View file

@ -7,13 +7,23 @@
<module-name>service-account-portal</module-name>
<servlet>
<servlet-name>ServiceAccountExample</servlet-name>
<servlet-class>org.keycloak.example.ProductServiceAccountServlet</servlet-class>
<servlet-name>ProductSAClientSecretServlet</servlet-name>
<servlet-class>org.keycloak.example.ProductSAClientSecretServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>ProductSAClientSignedJWTServlet</servlet-name>
<servlet-class>org.keycloak.example.ProductSAClientSignedJWTServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ServiceAccountExample</servlet-name>
<url-pattern>/app/*</url-pattern>
<servlet-name>ProductSAClientSecretServlet</servlet-name>
<url-pattern>/app-secret/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>ProductSAClientSignedJWTServlet</servlet-name>
<url-pattern>/app-jwt/*</url-pattern>
</servlet-mapping>
</web-app>

View file

@ -1,5 +1,9 @@
<html>
<head>
<meta http-equiv="Refresh" content="0; URL=app">
</head>
<head><title>Service account example</title></head>
<body>
<ul>
<li><a href="app-secret">Client authentication with client secret</a><br /><br /></li>
<li><a href="app-jwt">Client authentication with JWT signed by client private key</a></li>
</ul>
</body>
</html>

View file

@ -173,7 +173,10 @@
"clientId": "product-sa-client",
"enabled": true,
"secret": "password",
"serviceAccountsEnabled": true
"serviceAccountsEnabled": true,
"attributes": {
"jwt.credential.certificate": "MIICnTCCAYUCBgFPPLDaTzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE3MjI0N1oXDTI1MDgxNzE3MjQyN1owEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIUjjgv+V3s96O+Za9002Lp/trtGuHBeaeVL9dFKMKzO2MPqdRmHB4PqNlDdd28Rwf5Xn6iWdFpyUKOnI/yXDLhdcuFpR0sMNK/C9Lt+hSpPFLuzDqgtPgDotlMxiHIWDOZ7g9/gPYNXbNvjv8nSiyqoguoCQiiafW90bPHsiVLdP7ZIUwCcfi1qQm7FhxRJ1NiW5dvUkuCnnWEf0XR+Wzc5eC9EgB0taLFiPsSEIlWMm5xlahYyXkPdNOqZjiRnrTWm5Y4uk8ZcsD/KbPTf/7t7cQXipVaswgjdYi1kK2/zRwOhg1QwWFX/qmvdd+fLxV0R6VqRDhn7Qep2cxwMxLsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAKE6OA46sf20bz8LZPoiNsqRwBUDkaMGXfnob7s/hJZIIwDEx0IAQ3uKsG7q9wb+aA6s+v7S340zb2k3IxuhFaHaZpAd4CyR5cn1FHylbzoZ7rI/3ASqHDqpljdJaFqPH+m7nZWtyDvtZf+gkZ8OjsndwsSBK1d/jMZPp29qYbl1+XfO7RCp/jDqro/R3saYFaIFiEZPeKn1hUJn6BO48vxH1xspSu9FmlvDOEAOz4AuM58z4zRMP49GcFdCWr1wkonJUHaSptJaQwmBwLFUkCbE5I1ixGMb7mjEud6Y5jhfzJiZMo2U8RfcjNbrN0diZl3jB6LQIwESnhYSghaTjNQ=="
}
}
],
"clientScopeMappings": {

View file

@ -41,7 +41,6 @@ public class AppContextListener implements ServletContextListener {
}
ServletOAuthClientBuilder.build(is, oauthClient);
logger.info("OAuth client configured and started");
oauthClient.start();
}
@Override

View file

@ -27,8 +27,6 @@ public class Bootstrap implements ServletContextListener {
ServletContext context = sce.getServletContext();
configureClient(context);
client.start();
context.setAttribute(ServletOAuthClient.class.getName(), client);
}

View file

@ -654,7 +654,7 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'ClientSecretCtrl'
})
.when('/realms/:realm/clients/:client/credentials/client-signed-jwt', {
.when('/realms/:realm/clients/:client/credentials/client-jwt', {
templateUrl : resourceUrl + '/partials/client-credentials-jwt.html',
resolve : {
realm : function(RealmLoader) {
@ -666,7 +666,7 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'ClientSignedJWTCtrl'
})
.when('/realms/:realm/clients/:client/credentials/client-signed-jwt/:keyType/import/:attribute', {
.when('/realms/:realm/clients/:client/credentials/client-jwt/:keyType/import/:attribute', {
templateUrl : resourceUrl + '/partials/client-credentials-jwt-key-import.html',
resolve : {
realm : function(RealmLoader) {
@ -681,7 +681,7 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'ClientCertificateImportCtrl'
})
.when('/realms/:realm/clients/:client/credentials/client-signed-jwt/:keyType/export/:attribute', {
.when('/realms/:realm/clients/:client/credentials/client-jwt/:keyType/export/:attribute', {
templateUrl : resourceUrl + '/partials/client-credentials-jwt-key-export.html',
resolve : {
realm : function(RealmLoader) {

View file

@ -81,11 +81,11 @@ module.controller('ClientSignedJWTCtrl', function($scope, $location, realm, clie
);
$scope.importCertificate = function() {
$location.url("/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-signed-jwt/Signing/import/jwt.credentials");
$location.url("/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-jwt/Signing/import/jwt.credentials");
};
$scope.generateSigningKey = function() {
$location.url("/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-signed-jwt/Signing/export/jwt.credentials");
$location.url("/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-jwt/Signing/export/jwt.credentials");
};
$scope.cancel = function() {
@ -263,7 +263,7 @@ module.controller('ClientCertificateImportCtrl', function($scope, $location, $ht
var redirectLocation = "/realms/" + realm.realm + "/clients/" + client.id + "/saml/keys";
} else if (callingContext == 'jwt-credentials') {
var uploadUrl = authUrl + '/admin/realms/' + realm.realm + '/clients/' + client.id + '/certificates/' + attribute + '/upload-certificate';
var redirectLocation = "/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-signed-jwt";
var redirectLocation = "/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-jwt";
}
$scope.files = [];
@ -371,6 +371,12 @@ module.controller('ClientCertificateExportCtrl', function($scope, $location, $ht
});
var ext = ".jks";
if ($scope.jks.format == 'PKCS12') ext = ".p12";
if (callingContext == 'jwt-credentials') {
$location.url("/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-jwt");
Notifications.success("New keypair and certificate generated successfully. Download keystore file")
}
saveAs(blob, 'keystore' + ext);
}).error(function(data) {
var errorMsg = 'Error downloading';
@ -390,7 +396,7 @@ module.controller('ClientCertificateExportCtrl', function($scope, $location, $ht
});
$scope.cancel = function() {
$location.url("/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-signed-jwt");
$location.url("/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-jwt");
}
});

View file

@ -1622,7 +1622,15 @@ module.controller('IdentityProviderMapperCreateCtrl', function($scope, realm, id
});
module.controller('RealmFlowBindingCtrl', function($scope, flows, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications) {
$scope.flows = flows;
$scope.flows = [];
$scope.clientFlows = [];
for (var i=0 ; i<flows.length ; i++) {
if (flows[i].providerId == 'client-flow') {
$scope.clientFlows.push(flows[i]);
} else {
$scope.flows.push(flows[i]);
}
}
genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/authentication/flow-bindings");
});
@ -1792,7 +1800,7 @@ module.controller('AuthenticationFlowsCtrl', function($scope, $route, realm, flo
};
$scope.addFlow = function() {
$location.url("/realms/" + realm.realm + '/authentication/flows/' + $scope.flow.id + '/create/flow/execution');
$location.url("/realms/" + realm.realm + '/authentication/flows/' + $scope.flow.id + '/create/flow/execution/' + $scope.flow.id);
}
@ -1807,7 +1815,7 @@ module.controller('AuthenticationFlowsCtrl', function($scope, $route, realm, flo
}
$scope.addExecution = function() {
$location.url("/realms/" + realm.realm + '/authentication/flows/' + $scope.flow.id + '/create/execution');
$location.url("/realms/" + realm.realm + '/authentication/flows/' + $scope.flow.id + '/create/execution/' + $scope.flow.id);
}

View file

@ -46,6 +46,17 @@
<kc-tooltip>Select the flow you want to use when the user has forgotten their credentials.</kc-tooltip>
</div>
<div class="form-group">
<label for="resetCredentials" class="col-md-2 control-label">Client Authentication</label>
<div class="col-md-2">
<div>
<select id="clientAuthentication" ng-model="realm.clientAuthenticationFlow" class="form-control" ng-options="flow.alias as flow.alias for flow in clientFlows">
</select>
</div>
</div>
<kc-tooltip>Select the flow you want to use for authentication of clients.</kc-tooltip>
</div>
<div class="form-group" data-ng-show="access.manageRealm">
<div class="col-md-12">
<button kc-save data-ng-disabled="!changed">Save</button>

View file

@ -3,7 +3,7 @@
<ol class="breadcrumb">
<li><a href="#/realms/{{realm.realm}}/clients">Clients</a></li>
<li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}">{{client.clientId}}</a></li>
<li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/credentials/client-signed-jwt">Signed JWT config</a></li>
<li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/credentials/client-jwt">Signed JWT config</a></li>
<li class="active">Generate Client Private Key</li>
</ol>

View file

@ -3,7 +3,7 @@
<ol class="breadcrumb">
<li><a href="#/realms/{{realm.realm}}/clients">Clients</a></li>
<li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}">{{client.clientId}}</a></li>
<li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/credentials/client-signed-jwt">Signed JWT config</a></li>
<li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/credentials/client-jwt">Signed JWT config</a></li>
<li class="active">Client Certificate Import</li>
</ol>

View file

@ -9,7 +9,7 @@
<form class="form-horizontal" name="keyForm" novalidate kc-read-only="!access.manageClients">
<fieldset class="form-group col-sm-10">
<legend uncollapsed><span class="text">Client Certificate</span> <kc-tooltip>Client Certificate for validate JWT issued by client and signed by Client private key.</kc-tooltip></legend>
<legend uncollapsed><span class="text">Client Certificate</span> <kc-tooltip>Client Certificate for validate JWT issued by client and signed by Client private key from your keystore.</kc-tooltip></legend>
<div class="form-group" data-ng-hide="!signingKeyInfo.certificate">
<label class="col-md-2 control-label" for="signingCert">Certificate</label>
@ -19,11 +19,11 @@
</div>
</div>
<div class="form-group" data-ng-show="!signingKeyInfo.certificate">
<label class="col-md-2 control-label" for="signingCert">Client Certificate not yet generated or imported!</label>
<label class="col-md-4 control-label" for="signingCert">Client Certificate not yet generated or imported!</label>
</div>
<div class="form-group">
<div class="col-md-10 col-md-offset-2" data-ng-show="access.manageClients">
<button class="btn btn-default" type="submit" data-ng-click="generateSigningKey()">Generate new keys</button>
<button class="btn btn-default" type="submit" data-ng-click="generateSigningKey()">Generate new keys and certificate</button>
<button class="btn btn-default" type="submit" data-ng-click="importCertificate()">Import certificate</button>
<button class="btn btn-default" type="buttin" data-ng-click="cancel()">Cancel</button>
</div>

View file

@ -29,7 +29,7 @@
</select>
</div>
</div>
<kc-tooltip>What kind of top level flow is it? Type 'client' is used for authentication of clients (applications) when generic is for everything else</kc-tooltip>
<kc-tooltip>What kind of top level flow is it? Type 'client' is used for authentication of clients (applications) when generic is for users and everything else</kc-tooltip>
</div>
<div class="form-group">
<div class="col-md-10 col-md-offset-2">

View file

@ -5,6 +5,7 @@ import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.jboss.logging.Logger;
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
import org.keycloak.enums.RelativeUrlsUsed;
import org.keycloak.enums.SslRequired;
import org.keycloak.enums.TokenStore;
@ -253,15 +254,25 @@ public class AdapterDeploymentContext {
}
@Override
public Map<String, String> getResourceCredentials() {
public Map<String, Object> getResourceCredentials() {
return delegate.getResourceCredentials();
}
@Override
public void setResourceCredentials(Map<String, String> resourceCredentials) {
public void setResourceCredentials(Map<String, Object> resourceCredentials) {
delegate.setResourceCredentials(resourceCredentials);
}
@Override
public void setClientAuthenticator(ClientCredentialsProvider clientAuthenticator) {
delegate.setClientAuthenticator(clientAuthenticator);
}
@Override
public ClientCredentialsProvider getClientAuthenticator() {
return delegate.getClientAuthenticator();
}
@Override
public HttpClient getClient() {
return delegate.getClient();

View file

@ -9,6 +9,7 @@ import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicNameValuePair;
import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.util.BasicAuthHelper;
@ -76,13 +77,7 @@ public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticat
formparams.add(new BasicNameValuePair("username", username));
formparams.add(new BasicNameValuePair("password", password));
if (deployment.isPublicClient()) {
formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, deployment.getResourceName()));
} else {
String authorization = BasicAuthHelper.createHeader(deployment.getResourceName(),
deployment.getResourceCredentials().get("secret"));
post.setHeader("Authorization", authorization);
}
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
post.setEntity(form);

View file

@ -1,50 +0,0 @@
package org.keycloak.adapters;
import java.io.InputStream;
import java.security.PrivateKey;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.util.FindFile;
import org.keycloak.util.KeystoreUtil;
import org.keycloak.util.Time;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ClientAuthAdapterUtils {
public static String createSignedJWT(KeycloakDeployment deployment) {
// TODO: Read all the config from KeycloakDeployment and call below
return null;
}
public static String createSignedJWT(String clientId, String realmInfoUrl,
String keystoreFile, String storePassword, String keyPassword, String alias, KeystoreUtil.KeystoreFormat type,
int tokenTimeout) {
JsonWebToken jwt = createRequestToken(clientId, realmInfoUrl, tokenTimeout);
PrivateKey privateKey = KeystoreUtil.loadPrivateKeyFromKeystore(keystoreFile, storePassword, keyPassword, alias, type);
String signedToken = new JWSBuilder()
.jsonContent(jwt)
.rsa256(privateKey);
return signedToken;
}
private static JsonWebToken createRequestToken(String clientId, String realmInfoUrl, int tokenTimeout) {
JsonWebToken reqToken = new JsonWebToken();
reqToken.id(AdapterUtils.generateId());
reqToken.issuer(clientId);
reqToken.audience(realmInfoUrl);
int now = Time.currentTime();
reqToken.issuedAt(now);
reqToken.expiration(now + tokenTimeout);
reqToken.notBefore(now);
return reqToken;
}
}

View file

@ -2,6 +2,7 @@ package org.keycloak.adapters;
import org.apache.http.client.HttpClient;
import org.jboss.logging.Logger;
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.enums.RelativeUrlsUsed;
import org.keycloak.enums.SslRequired;
@ -39,7 +40,8 @@ public class KeycloakDeployment {
protected boolean bearerOnly;
protected boolean enableBasicAuth;
protected boolean publicClient;
protected Map<String, String> resourceCredentials = new HashMap<String, String>();
protected Map<String, Object> resourceCredentials = new HashMap<>();
protected ClientCredentialsProvider clientAuthenticator;
protected HttpClient client;
protected String scope;
@ -216,14 +218,22 @@ public class KeycloakDeployment {
this.publicClient = publicClient;
}
public Map<String, String> getResourceCredentials() {
public Map<String, Object> getResourceCredentials() {
return resourceCredentials;
}
public void setResourceCredentials(Map<String, String> resourceCredentials) {
public void setResourceCredentials(Map<String, Object> resourceCredentials) {
this.resourceCredentials = resourceCredentials;
}
public ClientCredentialsProvider getClientAuthenticator() {
return clientAuthenticator;
}
public void setClientAuthenticator(ClientCredentialsProvider clientAuthenticator) {
this.clientAuthenticator = clientAuthenticator;
}
public HttpClient getClient() {
return client;
}

View file

@ -3,6 +3,7 @@ package org.keycloak.adapters;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.jboss.logging.Logger;
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.enums.SslRequired;
import org.keycloak.enums.TokenStore;
import org.keycloak.representations.adapters.config.AdapterConfig;
@ -55,7 +56,10 @@ public class KeycloakDeploymentBuilder {
deployment.setTokenStore(TokenStore.SESSION);
}
if (adapterConfig.getPrincipalAttribute() != null) deployment.setPrincipalAttribute(adapterConfig.getPrincipalAttribute());
deployment.setResourceCredentials(adapterConfig.getCredentials());
deployment.setClientAuthenticator(ClientCredentialsProviderUtils.bootstrapClientAuthenticator(deployment));
deployment.setPublicClient(adapterConfig.isPublicClient());
deployment.setUseResourceRoleMappings(adapterConfig.isUseResourceRoleMappings());

View file

@ -8,10 +8,9 @@ import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicNameValuePair;
import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.util.BasicAuthHelper;
import org.keycloak.util.HostUtils;
import org.keycloak.util.JsonSerialization;
import org.keycloak.util.KeycloakUriBuilder;
@ -23,7 +22,6 @@ import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -50,30 +48,17 @@ public class ServerRequest {
}
public static void invokeLogout(KeycloakDeployment deployment, String refreshToken) throws IOException, HttpFailure {
String client_id = deployment.getResourceName();
Map<String, String> credentials = deployment.getResourceCredentials();
HttpClient client = deployment.getClient();
URI uri = deployment.getLogoutUrl().clone().build();
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
for (Map.Entry<String, String> entry : credentials.entrySet()) {
formparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
List<NameValuePair> formparams = new ArrayList<>();
formparams.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken));
HttpResponse response = null;
HttpPost post = new HttpPost(uri);
if (!deployment.isPublicClient()) {
String clientSecret = credentials.get(CredentialRepresentation.SECRET);
if (clientSecret != null) {
String authorization = BasicAuthHelper.createHeader(client_id, clientSecret);
post.setHeader("Authorization", authorization);
}
} else {
formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, client_id));
}
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
post.setEntity(form);
response = client.execute(post);
HttpResponse response = client.execute(post);
int status = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
if (status != 204) {
@ -86,17 +71,8 @@ public class ServerRequest {
if (is != null) is.close();
}
public static AccessTokenResponse invokeAccessCodeToToken(KeycloakDeployment deployment, String code, String redirectUri, String sessionId) throws HttpFailure, IOException {
String tokenUrl = deployment.getTokenUrl();
String client_id = deployment.getResourceName();
Map<String, String> credentials = deployment.getResourceCredentials();
HttpClient client = deployment.getClient();
return invokeAccessCodeToToken(client, deployment.isPublicClient(), code, tokenUrl, redirectUri, client_id, credentials, sessionId);
}
public static AccessTokenResponse invokeAccessCodeToToken(HttpClient client, boolean publicClient, String code, String tokenUrl, String redirectUri, String client_id, Map<String, String> credentials, String sessionId) throws IOException, HttpFailure {
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
public static AccessTokenResponse invokeAccessCodeToToken(KeycloakDeployment deployment, String code, String redirectUri, String sessionId) throws IOException, HttpFailure {
List<NameValuePair> formparams = new ArrayList<>();
redirectUri = stripOauthParametersFromRedirect(redirectUri);
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, "authorization_code"));
formparams.add(new BasicNameValuePair(OAuth2Constants.CODE, code));
@ -105,21 +81,13 @@ public class ServerRequest {
formparams.add(new BasicNameValuePair(AdapterConstants.CLIENT_SESSION_STATE, sessionId));
formparams.add(new BasicNameValuePair(AdapterConstants.CLIENT_SESSION_HOST, HostUtils.getHostName()));
}
HttpResponse response = null;
HttpPost post = new HttpPost(tokenUrl);
if (!publicClient) {
String clientSecret = credentials.get(CredentialRepresentation.SECRET);
if (clientSecret != null) {
String authorization = BasicAuthHelper.createHeader(client_id, clientSecret);
post.setHeader("Authorization", authorization);
}
} else {
formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, client_id));
}
HttpPost post = new HttpPost(deployment.getTokenUrl());
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
post.setEntity(form);
response = client.execute(post);
HttpResponse response = deployment.getClient().execute(post);
int status = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
if (status != 200) {
@ -152,36 +120,16 @@ public class ServerRequest {
}
public static AccessTokenResponse invokeRefresh(KeycloakDeployment deployment, String refreshToken) throws IOException, HttpFailure {
String tokenUrl = deployment.getTokenUrl();
String client_id = deployment.getResourceName();
Map<String, String> credentials = deployment.getResourceCredentials();
HttpClient client = deployment.getClient();
return invokeRefresh(client, deployment.isPublicClient(), refreshToken, tokenUrl, client_id, credentials);
}
public static AccessTokenResponse invokeRefresh(HttpClient client, boolean publicClient, String refreshToken, String tokenUrl, String client_id, Map<String, String> credentials) throws IOException, HttpFailure {
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
for (Map.Entry<String, String> entry : credentials.entrySet()) {
formparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN));
formparams.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken));
HttpResponse response = null;
HttpPost post = new HttpPost(tokenUrl);
if (!publicClient) {
String clientSecret = credentials.get(CredentialRepresentation.SECRET);
if (clientSecret != null) {
String authorization = BasicAuthHelper.createHeader(client_id, clientSecret);
post.setHeader("Authorization", authorization);
}
} else {
formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, client_id));
}
HttpPost post = new HttpPost(deployment.getTokenUrl());
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
post.setEntity(form);
response = client.execute(post);
HttpResponse response = deployment.getClient().execute(post);
int status = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
if (status != 200) {
@ -215,43 +163,28 @@ public class ServerRequest {
public static void invokeRegisterNode(KeycloakDeployment deployment, String host) throws HttpFailure, IOException {
String registerNodeUrl = deployment.getRegisterNodeUrl();
String client_id = deployment.getResourceName();
Map<String, String> credentials = deployment.getResourceCredentials();
HttpClient client = deployment.getClient();
invokeClientManagementRequest(client, host, registerNodeUrl, client_id, credentials);
invokeClientManagementRequest(deployment, host, registerNodeUrl);
}
public static void invokeUnregisterNode(KeycloakDeployment deployment, String host) throws HttpFailure, IOException {
String unregisterNodeUrl = deployment.getUnregisterNodeUrl();
String client_id = deployment.getResourceName();
Map<String, String> credentials = deployment.getResourceCredentials();
HttpClient client = deployment.getClient();
invokeClientManagementRequest(client, host, unregisterNodeUrl, client_id, credentials);
invokeClientManagementRequest(deployment, host, unregisterNodeUrl);
}
public static void invokeClientManagementRequest(HttpClient client, String host, String endpointUrl, String clientId, Map<String, String> credentials) throws HttpFailure, IOException {
public static void invokeClientManagementRequest(KeycloakDeployment deployment, String host, String endpointUrl) throws HttpFailure, IOException {
if (endpointUrl == null) {
throw new IOException("You need to configure URI for register/unregister node for application " + clientId);
throw new IOException("You need to configure URI for register/unregister node for application " + deployment.getResourceName());
}
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair(AdapterConstants.CLIENT_CLUSTER_HOST, host));
HttpPost post = new HttpPost(endpointUrl);
String clientSecret = credentials.get(CredentialRepresentation.SECRET);
if (clientSecret != null) {
String authorization = BasicAuthHelper.createHeader(clientId, clientSecret);
post.setHeader("Authorization", authorization);
} else {
throw new IOException("You need to configure clientSecret for register/unregister node for application " + clientId);
}
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
post.setEntity(form);
HttpResponse response = client.execute(post);
HttpResponse response = deployment.getClient().execute(post);
int status = response.getStatusLine().getStatusCode();
if (status != 204) {
HttpEntity entity = response.getEntity();

View file

@ -0,0 +1,19 @@
package org.keycloak.adapters.authentication;
import java.util.Map;
import org.keycloak.adapters.KeycloakDeployment;
/**
* TODO: Javadoc
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface ClientCredentialsProvider {
String getId();
void init(KeycloakDeployment deployment, Object config);
void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formParams);
}

View file

@ -0,0 +1,85 @@
package org.keycloak.adapters.authentication;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import org.apache.http.NameValuePair;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicNameValuePair;
import org.jboss.logging.Logger;
import org.keycloak.adapters.KeycloakDeployment;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ClientCredentialsProviderUtils {
private static Logger logger = Logger.getLogger(ClientCredentialsProviderUtils.class);
public static ClientCredentialsProvider bootstrapClientAuthenticator(KeycloakDeployment deployment) {
String clientId = deployment.getResourceName();
Map<String, Object> clientCredentials = deployment.getResourceCredentials();
String authenticatorId;
if (clientCredentials == null || clientCredentials.isEmpty()) {
authenticatorId = ClientIdAndSecretCredentialsProvider.PROVIDER_ID;
} else {
authenticatorId = (String) clientCredentials.get("provider");
if (authenticatorId == null) {
// If there is just one credential type, use provider from it
if (clientCredentials.size() == 1) {
authenticatorId = clientCredentials.keySet().iterator().next();
} else {
throw new RuntimeException("Can't identify clientAuthenticator from the configuration of client '" + clientId + "' . Check your adapter configurations");
}
}
}
logger.debugf("Using provider '%s' for authentication of client '%s'", authenticatorId, clientId);
Map<String, ClientCredentialsProvider> authenticators = new HashMap<>();
loadAuthenticators(authenticators, ClientCredentialsProviderUtils.class.getClassLoader());
loadAuthenticators(authenticators, Thread.currentThread().getContextClassLoader());
ClientCredentialsProvider authenticator = authenticators.get(authenticatorId);
if (authenticator == null) {
throw new RuntimeException("Couldn't find ClientCredentialsProvider implementation class with id: " + authenticatorId + ". Loaded authentication providers: " + authenticators.keySet());
}
Object config = (clientCredentials==null) ? null : clientCredentials.get(authenticatorId);
authenticator.init(deployment, config);
return authenticator;
}
private static void loadAuthenticators(Map<String, ClientCredentialsProvider> authenticators, ClassLoader classLoader) {
for (ClientCredentialsProvider authenticator : ServiceLoader.load(ClientCredentialsProvider.class, classLoader)) {
authenticators.put(authenticator.getId(), authenticator);
}
}
public static void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formparams) {
ClientCredentialsProvider authenticator = deployment.getClientAuthenticator();
authenticator.setClientCredentials(deployment, requestHeaders, formparams);
}
/**
* Don't use directly from your JEE apps to avoid HttpClient linkage errors! Instead use the method {@link #setClientCredentials(KeycloakDeployment, Map, Map)}
*/
public static void setClientCredentials(KeycloakDeployment deployment, HttpPost post, List<NameValuePair> formparams) {
Map<String, String> reqHeaders = new HashMap<>();
Map<String, String> reqParams = new HashMap<>();
setClientCredentials(deployment, reqHeaders, reqParams);
for (Map.Entry<String, String> header : reqHeaders.entrySet()) {
post.setHeader(header.getKey(), header.getValue());
}
for (Map.Entry<String, String> param : reqParams.entrySet()) {
formparams.add(new BasicNameValuePair(param.getKey(), param.getValue()));
}
}
}

View file

@ -0,0 +1,49 @@
package org.keycloak.adapters.authentication;
import java.util.Map;
import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.util.BasicAuthHelper;
/**
* Traditional OAuth2 authentication of clients based on client_id and client_secret
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ClientIdAndSecretCredentialsProvider implements ClientCredentialsProvider {
private static Logger logger = Logger.getLogger(ClientCredentialsProviderUtils.class);
public static final String PROVIDER_ID = CredentialRepresentation.SECRET;
private String clientSecret;
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public void init(KeycloakDeployment deployment, Object config) {
clientSecret = (String) config;
}
@Override
public void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formParams) {
String clientId = deployment.getResourceName();
if (!deployment.isPublicClient()) {
if (clientSecret != null) {
String authorization = BasicAuthHelper.createHeader(clientId, clientSecret);
requestHeaders.put("Authorization", authorization);
} else {
logger.warnf("Client '%s' doesn't have secret available", clientId);
}
} else {
formParams.put(OAuth2Constants.CLIENT_ID, clientId);
}
}
}

View file

@ -0,0 +1,103 @@
package org.keycloak.adapters.authentication;
import java.security.PrivateKey;
import java.util.Map;
import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.AdapterUtils;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.util.KeystoreUtil;
import org.keycloak.util.Time;
/**
* Client authentication based on JWT signed by client private key
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
public static final String PROVIDER_ID = "jwt";
private PrivateKey privateKey;
private int tokenTimeout;
@Override
public String getId() {
return PROVIDER_ID;
}
public void setPrivateKey(PrivateKey privateKey) {
this.privateKey = privateKey;
}
public void setTokenTimeout(int tokenTimeout) {
this.tokenTimeout = tokenTimeout;
}
@Override
public void init(KeycloakDeployment deployment, Object config) {
if (config == null || !(config instanceof Map)) {
throw new RuntimeException("Configuration of jwt credentials is missing or incorrect for client '" + deployment.getResourceName() + "'. Check your adapter configuration");
}
Map<String, Object> cfg = (Map<String, Object>) config;
String clientKeystoreFile = (String) cfg.get("client-keystore-file");
if (clientKeystoreFile == null) {
throw new RuntimeException("Missing parameter client-keystore-file in configuration of jwt for client " + deployment.getResourceName());
}
String clientKeystoreType = (String) cfg.get("client-keystore-type");
KeystoreUtil.KeystoreFormat clientKeystoreFormat = clientKeystoreType==null ? KeystoreUtil.KeystoreFormat.JKS : Enum.valueOf(KeystoreUtil.KeystoreFormat.class, clientKeystoreType);
String clientKeystorePassword = (String) cfg.get("client-keystore-password");
if (clientKeystorePassword == null) {
throw new RuntimeException("Missing parameter client-keystore-password in configuration of jwt for client " + deployment.getResourceName());
}
String clientKeyPassword = (String) cfg.get("client-key-password");
if (clientKeyPassword == null) {
clientKeyPassword = clientKeystorePassword;
}
String clientKeyAlias = (String) cfg.get("client-key-alias");
if (clientKeyAlias == null) {
clientKeyAlias = deployment.getResourceName();
}
this.privateKey = KeystoreUtil.loadPrivateKeyFromKeystore(clientKeystoreFile, clientKeystorePassword, clientKeyPassword, clientKeyAlias, clientKeystoreFormat);
Integer tokenExp = (Integer) cfg.get("token-timeout");
this.tokenTimeout = (tokenExp==null) ? 10 : tokenExp;
}
@Override
public void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formParams) {
String signedToken = createSignedRequestToken(deployment.getResourceName(), deployment.getRealmInfoUrl());
formParams.put(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT);
formParams.put(OAuth2Constants.CLIENT_ASSERTION, signedToken);
}
public String createSignedRequestToken(String clientId, String realmInfoUrl) {
JsonWebToken jwt = createRequestToken(clientId, realmInfoUrl);
return new JWSBuilder()
.jsonContent(jwt)
.rsa256(privateKey);
}
protected JsonWebToken createRequestToken(String clientId, String realmInfoUrl) {
JsonWebToken reqToken = new JsonWebToken();
reqToken.id(AdapterUtils.generateId());
reqToken.issuer(clientId);
reqToken.audience(realmInfoUrl);
int now = Time.currentTime();
reqToken.issuedAt(now);
reqToken.expiration(now + this.tokenTimeout);
reqToken.notBefore(now);
return reqToken;
}
}

View file

@ -23,6 +23,7 @@ import org.apache.http.message.BasicNameValuePair;
import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants;
import org.keycloak.VerificationException;
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.util.BasicAuthHelper;
@ -72,14 +73,8 @@ public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule {
formparams.add(new BasicNameValuePair("username", username));
formparams.add(new BasicNameValuePair("password", password));
if (deployment.isPublicClient()) { // if client is public access type
formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, deployment.getResourceName()));
} else {
String clientId = deployment.getResourceName();
String clientSecret = deployment.getResourceCredentials().get("secret");
String authorization = BasicAuthHelper.createHeader(clientId, clientSecret);
post.setHeader("Authorization", authorization);
}
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
post.setEntity(form);
@ -135,15 +130,7 @@ public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule {
HttpPost post = new HttpPost(logoutUri);
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
if (deployment.isPublicClient()) { // if client is public access type
formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, deployment.getResourceName()));
} else {
String clientId = deployment.getResourceName();
String clientSecret = deployment.getResourceCredentials().get("secret");
String authorization = BasicAuthHelper.createHeader(clientId, clientSecret);
post.setHeader("Authorization", authorization);
}
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
formparams.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken));
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");

View file

@ -0,0 +1,2 @@
org.keycloak.adapters.authentication.ClientIdAndSecretCredentialsProvider
org.keycloak.adapters.authentication.JWTClientCredentialsProvider

View file

@ -2,6 +2,9 @@ package org.keycloak.adapters;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.junit.Test;
import org.keycloak.adapters.authentication.ClientIdAndSecretCredentialsProvider;
import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
import org.keycloak.enums.RelativeUrlsUsed;
import org.keycloak.enums.SslRequired;
import org.keycloak.enums.TokenStore;
import org.keycloak.util.PemUtils;
@ -32,8 +35,10 @@ public class KeycloakDeploymentBuilderTest {
assertTrue(deployment.isEnableBasicAuth());
assertTrue(deployment.isExposeToken());
assertEquals("234234-234234-234234", deployment.getResourceCredentials().get("secret"));
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
assertEquals(20, ((ThreadSafeClientConnManager) deployment.getClient().getConnectionManager()).getMaxTotal());
assertEquals("https://backend:8443/auth/realms/demo/protocol/openid-connect/token", deployment.getTokenUrl());
assertEquals(RelativeUrlsUsed.NEVER, deployment.getRelativeUrls());
assertTrue(deployment.isAlwaysRefreshToken());
assertTrue(deployment.isRegisterNodeAtStartup());
assertEquals(1000, deployment.getRegisterNodePeriod());
@ -41,4 +46,16 @@ public class KeycloakDeploymentBuilderTest {
assertEquals("email", deployment.getPrincipalAttribute());
}
@Test
public void loadNoClientCredentials() throws Exception {
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-no-credentials.json"));
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
}
@Test
public void loadJwtCredentials() throws Exception {
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-jwt.json"));
assertEquals(JWTClientCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
}
}

View file

@ -0,0 +1,13 @@
{
"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"
}
}
}

View file

@ -0,0 +1,8 @@
{
"realm": "demo",
"resource": "customer-portal",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "https://localhost:8443/auth",
"public-client": true,
"expose-token": true
}

View file

@ -22,8 +22,8 @@
"truststore": "classpath:/cacerts.jks",
"truststore-password": "changeit",
"client-keystore": "classpath:/keystore.jks",
"client-keystore-password": "changeit",
"client-key-password": "password",
"client-keystore-password": "storepass",
"client-key-password": "keypass",
"auth-server-url-for-backend-requests": "https://backend:8443/auth",
"always-refresh-token": true,
"register-node-at-startup": true,

View file

@ -50,8 +50,8 @@ public class JaxrsOAuthClient extends AbstractOAuthClient {
.param(OAuth2Constants.CODE, code)
.param(OAuth2Constants.CLIENT_ID, clientId)
.param(OAuth2Constants.REDIRECT_URI, redirectUri);
for (Map.Entry<String, String> entry : credentials.entrySet()) {
codeForm.param(entry.getKey(), entry.getValue());
for (Map.Entry<String, Object> entry : credentials.entrySet()) {
codeForm.param(entry.getKey(), (String) entry.getValue());
}
Response res = client.target(tokenUrl).request().post(Entity.form(codeForm));
try {

View file

@ -44,6 +44,27 @@
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-xc</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>${jboss.logging.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>

View file

@ -0,0 +1,83 @@
package org.keycloak.servlet;
import java.util.Map;
import org.keycloak.AbstractOAuthClient;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.enums.RelativeUrlsUsed;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class KeycloakDeploymentDelegateOAuthClient extends AbstractOAuthClient {
private KeycloakDeployment deployment;
public KeycloakDeployment getDeployment() {
return deployment;
}
public void setDeployment(KeycloakDeployment deployment) {
this.deployment = deployment;
}
@Override
public String getClientId() {
return deployment.getResourceName();
}
@Override
public void setClientId(String clientId) {
deployment.setResourceName(clientId);
}
@Override
public Map<String, Object> getCredentials() {
return deployment.getResourceCredentials();
}
@Override
public void setCredentials(Map<String, Object> credentials) {
deployment.setResourceCredentials(credentials);
}
@Override
public String getAuthUrl() {
return deployment.getAuthUrl().clone().build().toString();
}
@Override
public void setAuthUrl(String authUrl) {
throw new IllegalStateException("Illegal to call this method");
}
@Override
public String getTokenUrl() {
return deployment.getTokenUrl();
}
@Override
public void setTokenUrl(String tokenUrl) {
throw new IllegalStateException("Illegal to call this method");
}
@Override
public boolean isPublicClient() {
return deployment.isPublicClient();
}
@Override
public void setPublicClient(boolean publicClient) {
deployment.setPublicClient(publicClient);
}
@Override
public RelativeUrlsUsed getRelativeUrlsUsed() {
return deployment.getRelativeUrls();
}
@Override
public void setRelativeUrlsUsed(RelativeUrlsUsed relativeUrlsUsed) {
throw new IllegalStateException("Illegal to call this method");
}
}

View file

@ -1,14 +1,10 @@
package org.keycloak.servlet;
import org.apache.http.client.HttpClient;
import org.keycloak.AbstractOAuthClient;
import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.HttpClientBuilder;
import org.keycloak.adapters.ServerRequest;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.util.KeycloakUriBuilder;
import org.keycloak.util.UriUtils;
@ -22,24 +18,18 @@ import java.net.URI;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class ServletOAuthClient extends AbstractOAuthClient {
protected HttpClient client;
protected AdapterConfig adapterConfig;
public void start() {
client = new HttpClientBuilder().build(adapterConfig);
}
public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
/**
* closes client
*/
public void stop() {
client.getConnectionManager().shutdown();
getDeployment().getClient().getConnectionManager().shutdown();
}
private AccessTokenResponse resolveBearerToken(HttpServletRequest request, String redirectUri, String code) throws IOException, ServerRequest.HttpFailure {
// Don't send sessionId in oauth clients for now
return ServerRequest.invokeAccessCodeToToken(client, publicClient, code, getUrl(request, tokenUrl, false), redirectUri, clientId, credentials, null);
return ServerRequest.invokeAccessCodeToToken(getDeployment(), code, redirectUri, null);
}
/**
@ -146,7 +136,7 @@ public class ServletOAuthClient extends AbstractOAuthClient {
}
public AccessTokenResponse refreshToken(HttpServletRequest request, String refreshToken) throws IOException, ServerRequest.HttpFailure {
return ServerRequest.invokeRefresh(client, publicClient, refreshToken, getUrl(request, tokenUrl, false), clientId, credentials);
return ServerRequest.invokeRefresh(getDeployment(), refreshToken);
}
public static IDToken extractIdToken(String idToken) {
@ -167,8 +157,4 @@ public class ServletOAuthClient extends AbstractOAuthClient {
return url;
}
}
public void setAdapterConfig(AdapterConfig adapterConfig) {
this.adapterConfig = adapterConfig;
}
}

View file

@ -1,14 +1,8 @@
package org.keycloak.servlet;
import org.apache.http.client.HttpClient;
import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.adapters.HttpClientBuilder;
import org.keycloak.enums.RelativeUrlsUsed;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.util.JsonSerialization;
import org.keycloak.util.KeycloakUriBuilder;
import java.io.IOException;
import java.io.InputStream;
/**
@ -18,62 +12,21 @@ import java.io.InputStream;
public class ServletOAuthClientBuilder {
public static ServletOAuthClient build(InputStream is) {
AdapterConfig adapterConfig = getAdapterConfig(is);
return build(adapterConfig);
}
public static AdapterConfig getAdapterConfig(InputStream is) {
try {
return JsonSerialization.readValue(is, AdapterConfig.class, true);
} catch (IOException e) {
throw new RuntimeException(e);
}
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(is);
ServletOAuthClient client = new ServletOAuthClient();
client.setDeployment(deployment);
return client;
}
public static ServletOAuthClient build(AdapterConfig adapterConfig) {
ServletOAuthClient oauthClient = new ServletOAuthClient();
build(adapterConfig, oauthClient);
return oauthClient;
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(adapterConfig);
ServletOAuthClient client = new ServletOAuthClient();
client.setDeployment(deployment);
return client;
}
public static void build(InputStream is, ServletOAuthClient oauthClient) {
build(getAdapterConfig(is), oauthClient);
}
public static void build(AdapterConfig adapterConfig, ServletOAuthClient oauthClient) {
oauthClient.setAdapterConfig(adapterConfig);
oauthClient.setClientId(adapterConfig.getResource());
oauthClient.setPublicClient(adapterConfig.isPublicClient());
oauthClient.setCredentials(adapterConfig.getCredentials());
if (adapterConfig.getAuthServerUrl() == null) {
throw new RuntimeException("You must specify auth-url");
}
KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(adapterConfig.getAuthServerUrl());
RelativeUrlsUsed useRelative = relativeUrls(serverBuilder, adapterConfig);
oauthClient.setRelativeUrlsUsed(useRelative);
String authUrl = serverBuilder.clone().path(ServiceUrlConstants.AUTH_PATH).build(adapterConfig.getRealm()).toString();
KeycloakUriBuilder tokenUrlBuilder;
if (useRelative == RelativeUrlsUsed.BROWSER_ONLY) {
// Use absolute URI for refreshToken and codeToToken requests
KeycloakUriBuilder nonBrowsersServerBuilder = KeycloakUriBuilder.fromUri(adapterConfig.getAuthServerUrlForBackendRequests());
tokenUrlBuilder = nonBrowsersServerBuilder.clone();
} else {
tokenUrlBuilder = serverBuilder.clone();
}
String tokenUrl = tokenUrlBuilder.path(ServiceUrlConstants.TOKEN_PATH).build(adapterConfig.getRealm()).toString();
oauthClient.setAuthUrl(authUrl);
oauthClient.setTokenUrl(tokenUrl);
}
private static RelativeUrlsUsed relativeUrls(KeycloakUriBuilder serverBuilder, AdapterConfig adapterConfig) {
if (serverBuilder.clone().getHost() == null) {
return (adapterConfig.getAuthServerUrlForBackendRequests() != null) ? RelativeUrlsUsed.BROWSER_ONLY : RelativeUrlsUsed.ALL_REQUESTS;
} else {
return RelativeUrlsUsed.NEVER;
}
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(is);
oauthClient.setDeployment(deployment);
}
}

View file

@ -0,0 +1,25 @@
package org.keycloak.servlet;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.enums.RelativeUrlsUsed;
import org.keycloak.representations.idm.CredentialRepresentation;
import static org.junit.Assert.assertEquals;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ServletOAuthClientBuilderTest {
@Test
public void testBuilder() {
ServletOAuthClient oauthClient = ServletOAuthClientBuilder.build(getClass().getResourceAsStream("/keycloak.json"));
Assert.assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/auth", oauthClient.getAuthUrl());
Assert.assertEquals("https://backend:8443/auth/realms/demo/protocol/openid-connect/token", oauthClient.getTokenUrl());
assertEquals(RelativeUrlsUsed.NEVER, oauthClient.getRelativeUrlsUsed());
Assert.assertEquals("customer-portal", oauthClient.getClientId());
Assert.assertEquals("234234-234234-234234", oauthClient.getCredentials().get(CredentialRepresentation.SECRET));
Assert.assertEquals(true, oauthClient.isPublicClient());
}
}

View file

@ -0,0 +1,28 @@
{
"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",
"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,
"auth-server-url-for-backend-requests": "https://backend:8443/auth",
"always-refresh-token": true,
"register-node-at-startup": true,
"register-node-period": 1000,
"token-store": "cookie",
"principal-attribute": "email"
}

View file

@ -304,7 +304,7 @@ public class DefaultAuthenticationFlows {
execution = new AuthenticationExecutionModel();
execution.setParentFlow(clients.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
execution.setAuthenticator("client-signed-jwt");
execution.setAuthenticator("client-jwt");
execution.setPriority(20);
execution.setAuthenticatorFlow(false);
realm.addAuthenticatorExecution(execution);

View file

@ -33,6 +33,7 @@ import org.keycloak.util.Time;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -67,6 +68,7 @@ public class AuthenticationProcessor {
// Used for client authentication
protected ClientModel client;
protected Map<String, String> clientAuthAttributes = new HashMap<>();
public AuthenticationProcessor() {
}
@ -83,6 +85,10 @@ public class AuthenticationProcessor {
this.client = client;
}
public Map<String, String> getClientAuthAttributes() {
return clientAuthAttributes;
}
public ClientSessionModel getClientSession() {
return clientSession;
}
@ -341,6 +347,11 @@ public class AuthenticationProcessor {
AuthenticationProcessor.this.setClient(client);
}
@Override
public Map<String, String> getClientAuthAttributes() {
return AuthenticationProcessor.this.getClientAuthAttributes();
}
@Override
public ClientSessionModel getClientSession() {
return AuthenticationProcessor.this.getClientSession();

View file

@ -55,20 +55,6 @@ public class ClientAuthenticationFlow implements AuthenticationFlow {
AuthenticationFlow authenticationFlow;
authenticationFlow = processor.createFlowExecution(model.getFlowId(), model);
/*if (model.getFlowId() != null) {
authenticationFlow = processor.createFlowExecution(model.getFlowId(), model);
} else {
// Continue with the flow specific to authenticatedClient
ClientModel authenticatedClient = processor.getClient();
if (authenticatedClient != null) {
String clientFlowId = authenticatedClient.getClientAuthFlowId();
authenticationFlow = processor.createFlowExecution(clientFlowId, model);
} else {
throw new AuthenticationFlowException("Authenticated client required for: " + model.getAuthenticator(), AuthenticationFlowError.CLIENT_NOT_FOUND);
}
}*/
Response flowChallenge = authenticationFlow.processFlow();
if (flowChallenge == null) {
if (model.isAlternative()) alternativeSuccessful = true;

View file

@ -1,5 +1,7 @@
package org.keycloak.authentication;
import java.util.Map;
import org.keycloak.models.ClientModel;
/**
@ -22,4 +24,15 @@ public interface ClientAuthenticationFlowContext extends AbstractAuthenticationF
*/
void setClient(ClientModel client);
/**
* Return the map where the authenticators can put some additional state related to authenticated client and the context how was
* client authenticated (ie. attributes from client certificate etc). Map is writable, so you can add/remove items from it as needed.
*
* After successful authentication will be those state data put into UserSession notes. This allows you to configure
* UserSessionNote protocol mapper for your client, which will allow to map those state data into the access token available in the application
*
* @return
*/
Map<String, String> getClientAuthAttributes();
}

View file

@ -29,51 +29,4 @@ public class ClientAuthUtil {
return Response.status(status).entity(e).type(MediaType.APPLICATION_JSON_TYPE).build();
}
// Return client either from client_id parameter or from "username" send in "Authorization: Basic" header.
public static ClientModel getClientFromClientId(ClientAuthenticationFlowContext context) {
String client_id = null;
String authorizationHeader = context.getHttpRequest().getHttpHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
if (authorizationHeader != null) {
String[] usernameSecret = BasicAuthHelper.parseHeader(authorizationHeader);
if (usernameSecret != null) {
client_id = usernameSecret[0];
} else {
// Don't send 401 if client_id parameter was sent in request. For example IE may automatically send "Authorization: Negotiate" in XHR requests even for public clients
if (!formData.containsKey(OAuth2Constants.CLIENT_ID)) {
Response challengeResponse = Response.status(Response.Status.UNAUTHORIZED).header(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"" + context.getRealm().getName() + "\"").build();
context.challenge(challengeResponse);
return null;
}
}
}
if (client_id == null) {
client_id = formData.getFirst(OAuth2Constants.CLIENT_ID);
}
if (client_id == null) {
Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_client", "Missing client_id parameter");
context.challenge(challengeResponse);
return null;
}
context.getEvent().client(client_id);
ClientModel client = context.getRealm().getClientByClientId(client_id);
if (client == null) {
context.failure(AuthenticationFlowError.CLIENT_NOT_FOUND, null);
return null;
}
if (!client.isEnabled()) {
context.failure(AuthenticationFlowError.CLIENT_DISABLED, null);
return null;
}
return client;
}
}

View file

@ -8,6 +8,7 @@ import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.AuthenticationFlowError;
import org.keycloak.authentication.ClientAuthenticationFlowContext;
@ -39,21 +40,60 @@ public class ClientIdAndSecretAuthenticator extends AbstractClientAuthenticator
@Override
public void authenticateClient(ClientAuthenticationFlowContext context) {
ClientModel client = ClientAuthUtil.getClientFromClientId(context);
if (client == null) {
return;
} else {
context.setClient(client);
String client_id = null;
String clientSecret = null;
String authorizationHeader = context.getHttpRequest().getHttpHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
if (authorizationHeader != null) {
String[] usernameSecret = BasicAuthHelper.parseHeader(authorizationHeader);
if (usernameSecret != null) {
client_id = usernameSecret[0];
clientSecret = usernameSecret[1];
} else {
// Don't send 401 if client_id parameter was sent in request. For example IE may automatically send "Authorization: Negotiate" in XHR requests even for public clients
if (!formData.containsKey(OAuth2Constants.CLIENT_ID)) {
Response challengeResponse = Response.status(Response.Status.UNAUTHORIZED).header(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"" + context.getRealm().getName() + "\"").build();
context.challenge(challengeResponse);
return;
}
}
}
if (client_id == null) {
client_id = formData.getFirst(OAuth2Constants.CLIENT_ID);
clientSecret = formData.getFirst("client_secret");
}
if (client_id == null) {
Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_client", "Missing client_id parameter");
context.challenge(challengeResponse);
return;
}
context.getEvent().client(client_id);
ClientModel client = context.getRealm().getClientByClientId(client_id);
if (client == null) {
context.failure(AuthenticationFlowError.CLIENT_NOT_FOUND, null);
return;
}
if (!client.isEnabled()) {
context.failure(AuthenticationFlowError.CLIENT_DISABLED, null);
return;
}
context.setClient(client);
// Skip client_secret validation for public client
if (client.isPublicClient()) {
context.success();
return;
}
String clientSecret = getClientSecret(context);
if (clientSecret == null) {
Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "unauthorized_client", "Client secret not provided in request");
context.challenge(challengeResponse);
@ -75,30 +115,6 @@ public class ClientIdAndSecretAuthenticator extends AbstractClientAuthenticator
context.success();
}
protected String getClientSecret(ClientAuthenticationFlowContext context) {
String clientSecret = null;
String authorizationHeader = context.getHttpRequest().getHttpHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
if (authorizationHeader != null) {
String[] usernameSecret = BasicAuthHelper.parseHeader(authorizationHeader);
if (usernameSecret != null) {
clientSecret = usernameSecret[1];
}
}
if (clientSecret == null) {
clientSecret = formData.getFirst("client_secret");
}
return clientSecret;
}
protected void setError(AuthenticationFlowContext context, Response challengeResponse) {
context.getEvent().error(Errors.INVALID_CLIENT_CREDENTIALS);
context.failure(AuthenticationFlowError.INVALID_CLIENT_CREDENTIALS, challengeResponse);
}
@Override
public String getDisplayType() {
return "Client Id and Secret";

View file

@ -30,7 +30,7 @@ public class JWTClientAuthenticator extends AbstractClientAuthenticator {
protected static Logger logger = Logger.getLogger(JWTClientAuthenticator.class);
public static final String PROVIDER_ID = "client-signed-jwt";
public static final String PROVIDER_ID = "client-jwt";
public static final String CERTIFICATE_ATTR = "jwt.credential.certificate";
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {

View file

@ -1,88 +0,0 @@
package org.keycloak.authentication.authenticators.client;
import java.util.LinkedList;
import java.util.List;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
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.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.util.BasicAuthHelper;
/**
* TODO: Should be removed? Or allowed just per public clients?
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ValidateClientId extends AbstractClientAuthenticator {
public static final String PROVIDER_ID = "client-id";
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
AuthenticationExecutionModel.Requirement.REQUIRED
};
@Override
public void authenticateClient(ClientAuthenticationFlowContext context) {
ClientModel client = ClientAuthUtil.getClientFromClientId(context);
if (client == null) {
return;
}
context.setClient(client);
context.success();
}
@Override
public String getDisplayType() {
return "Client ID Validation";
}
@Override
public boolean isConfigurable() {
return false;
}
@Override
public boolean isConfigurablePerClient() {
return false;
}
@Override
public boolean requiresClient() {
return false;
}
@Override
public boolean configuredFor(KeycloakSession session, RealmModel realm, ClientModel client) {
return true;
}
@Override
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
return REQUIREMENT_CHOICES;
}
@Override
public String getHelpText() {
return "Validates the clientId supplied as a 'client_id' form parameter or in 'Authorization: Basic' header";
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return new LinkedList<>();
}
@Override
public String getId() {
return PROVIDER_ID;
}
}

View file

@ -1,139 +0,0 @@
package org.keycloak.protocol.oidc;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.ClientConnection;
import org.keycloak.OAuth2Constants;
import org.keycloak.constants.ServiceAccountConstants;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.Urls;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.Cors;
/**
* Endpoint for authenticate clients and retrieve service accounts
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ServiceAccountManager {
protected static final Logger logger = Logger.getLogger(ServiceAccountManager.class);
private TokenManager tokenManager;
private EventBuilder event;
private HttpRequest request;
private MultivaluedMap<String, String> formParams;
private KeycloakSession session;
private UriInfo uriInfo;
private ClientConnection clientConnection;
private ClientModel client;
private UserModel clientUser;
public ServiceAccountManager(TokenManager tokenManager, EventBuilder event, HttpRequest request,
MultivaluedMap<String, String> formParams, KeycloakSession session, ClientModel client) {
this.tokenManager = tokenManager;
this.event = event;
this.request = request;
this.formParams = formParams;
this.session = session;
this.client = client;
this.uriInfo = session.getContext().getUri();
this.clientConnection = session.getContext().getConnection();
}
public Response buildClientCredentialsGrant() {
checkClient();
return finishClientAuthorization();
}
protected void checkClient() {
if (client.isBearerOnly()) {
event.error(Errors.INVALID_CLIENT);
throw new ErrorResponseException("unauthorized_client", "Bearer-only client not allowed to retrieve service account", Response.Status.UNAUTHORIZED);
}
if (client.isPublicClient()) {
event.error(Errors.INVALID_CLIENT);
throw new ErrorResponseException("unauthorized_client", "Public client not allowed to retrieve service account", Response.Status.UNAUTHORIZED);
}
if (!client.isServiceAccountsEnabled()) {
event.error(Errors.INVALID_CLIENT);
throw new ErrorResponseException("unauthorized_client", "Client not enabled to retrieve service account", Response.Status.UNAUTHORIZED);
}
}
protected Response finishClientAuthorization() {
RealmModel realm = client.getRealm();
event.detail(Details.RESPONSE_TYPE, ServiceAccountConstants.CLIENT_AUTH);
clientUser = session.users().getUserByServiceAccountClient(client);
if (clientUser == null || client.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, ServiceAccountConstants.CLIENT_ID_PROTOCOL_MAPPER) == null) {
// May need to handle bootstrap here as well
logger.infof("Service account user for client '%s' not found or default protocol mapper for service account not found. Creating now", client.getClientId());
new ClientManager(new RealmManager(session)).enableServiceAccount(client);
clientUser = session.users().getUserByServiceAccountClient(client);
}
String clientUsername = clientUser.getUsername();
event.detail(Details.USERNAME, clientUsername);
event.user(clientUser);
if (!clientUser.isEnabled()) {
event.error(Errors.USER_DISABLED);
throw new ErrorResponseException("invalid_request", "User '" + clientUsername + "' disabled", Response.Status.UNAUTHORIZED);
}
String scope = formParams.getFirst(OAuth2Constants.SCOPE);
UserSessionProvider sessions = session.sessions();
// TODO: Once more requirements are added, clientSession will be likely created earlier by authentication mechanism
ClientSessionModel clientSession = sessions.createClientSession(realm, client);
clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL);
clientSession.setNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
// TODO: Should rather obtain authMethod from client session?
UserSessionModel userSession = sessions.createUserSession(realm, clientUser, clientUsername, clientConnection.getRemoteAddr(), ServiceAccountConstants.CLIENT_AUTH, false, null, null);
event.session(userSession);
TokenManager.attachClientSession(userSession, clientSession);
// Notes about client details
userSession.setNote(ServiceAccountConstants.CLIENT_ID, client.getClientId());
userSession.setNote(ServiceAccountConstants.CLIENT_HOST, clientConnection.getRemoteHost());
userSession.setNote(ServiceAccountConstants.CLIENT_ADDRESS, clientConnection.getRemoteAddr());
AccessTokenResponse res = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession)
.generateAccessToken(session, scope, client, clientUser, userSession, clientSession)
.generateRefreshToken()
.generateIDToken()
.build();
event.success();
return Cors.add(request, Response.ok(res, MediaType.APPLICATION_JSON_TYPE)).auth().allowedOrigins(client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
}
}

View file

@ -190,7 +190,7 @@ public class LogoutEndpoint {
}
private ClientModel authorizeClient() {
ClientModel client = AuthorizeClientUtil.authorizeClient(session, event, realm);
ClientModel client = AuthorizeClientUtil.authorizeClient(session, event, realm).getClient();
if (client.isBearerOnly()) {
throw new ErrorResponseException("invalid_client", "Bearer-only not allowed", Response.Status.BAD_REQUEST);

View file

@ -7,6 +7,7 @@ import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException;
import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.constants.ServiceAccountConstants;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
@ -19,17 +20,16 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.utils.DefaultAuthenticationFlows;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.ServiceAccountManager;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.Cors;
import org.keycloak.services.Urls;
@ -52,6 +52,7 @@ public class TokenEndpoint {
private static final Logger logger = Logger.getLogger(TokenEndpoint.class);
private MultivaluedMap<String, String> formParams;
private ClientModel client;
private Map<String, String> clientAuthAttributes;
private enum Action {
AUTHORIZATION_CODE, REFRESH_TOKEN, PASSWORD, CLIENT_CREDENTIALS
@ -144,7 +145,9 @@ public class TokenEndpoint {
}
private void checkClient() {
client = AuthorizeClientUtil.authorizeClient(session, event, realm);
AuthorizeClientUtil.ClientAuthResult clientAuth = AuthorizeClientUtil.authorizeClient(session, event, realm);
client = clientAuth.getClient();
clientAuthAttributes = clientAuth.getClientAuthAttributes();
if (client.isBearerOnly()) {
throw new ErrorResponseException("invalid_client", "Bearer-only not allowed", Response.Status.BAD_REQUEST);
@ -237,6 +240,7 @@ public class TokenEndpoint {
}
updateClientSession(clientSession);
updateUserSessionFromClientAuth(userSession);
AccessToken token = tokenManager.createClientAccessToken(session, accessCode.getRequestedRoles(), realm, client, user, userSession, clientSession);
@ -262,6 +266,7 @@ public class TokenEndpoint {
UserSessionModel userSession = session.sessions().getUserSession(realm, res.getSessionState());
updateClientSessions(userSession.getClientSessions());
updateUserSessionFromClientAuth(userSession);
} catch (OAuthErrorException e) {
event.error(Errors.INVALID_TOKEN);
@ -312,6 +317,12 @@ public class TokenEndpoint {
}
}
private void updateUserSessionFromClientAuth(UserSessionModel userSession) {
for (Map.Entry<String, String> attr : clientAuthAttributes.entrySet()) {
userSession.setNote(attr.getKey(), attr.getValue());
}
}
public Response buildResourceOwnerPasswordCredentialsGrant() {
event.detail(Details.AUTH_METHOD, "oauth_credentials").detail(Details.RESPONSE_TYPE, "token");
@ -349,6 +360,7 @@ public class TokenEndpoint {
}
processor.attachSession();
UserSessionModel userSession = processor.getUserSession();
updateUserSessionFromClientAuth(userSession);
AccessTokenResponse res = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession)
.generateAccessToken(session, scope, client, user, userSession, clientSession)
@ -363,8 +375,68 @@ public class TokenEndpoint {
}
public Response buildClientCredentialsGrant() {
ServiceAccountManager serviceAccountManager = new ServiceAccountManager(tokenManager, event, request, formParams, session, client);
return serviceAccountManager.buildClientCredentialsGrant();
if (client.isBearerOnly()) {
event.error(Errors.INVALID_CLIENT);
throw new ErrorResponseException("unauthorized_client", "Bearer-only client not allowed to retrieve service account", Response.Status.UNAUTHORIZED);
}
if (client.isPublicClient()) {
event.error(Errors.INVALID_CLIENT);
throw new ErrorResponseException("unauthorized_client", "Public client not allowed to retrieve service account", Response.Status.UNAUTHORIZED);
}
if (!client.isServiceAccountsEnabled()) {
event.error(Errors.INVALID_CLIENT);
throw new ErrorResponseException("unauthorized_client", "Client not enabled to retrieve service account", Response.Status.UNAUTHORIZED);
}
event.detail(Details.RESPONSE_TYPE, ServiceAccountConstants.CLIENT_AUTH);
UserModel clientUser = session.users().getUserByServiceAccountClient(client);
if (clientUser == null || client.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, ServiceAccountConstants.CLIENT_ID_PROTOCOL_MAPPER) == null) {
// May need to handle bootstrap here as well
logger.infof("Service account user for client '%s' not found or default protocol mapper for service account not found. Creating now", client.getClientId());
new ClientManager(new RealmManager(session)).enableServiceAccount(client);
clientUser = session.users().getUserByServiceAccountClient(client);
}
String clientUsername = clientUser.getUsername();
event.detail(Details.USERNAME, clientUsername);
event.user(clientUser);
if (!clientUser.isEnabled()) {
event.error(Errors.USER_DISABLED);
throw new ErrorResponseException("invalid_request", "User '" + clientUsername + "' disabled", Response.Status.UNAUTHORIZED);
}
String scope = formParams.getFirst(OAuth2Constants.SCOPE);
UserSessionProvider sessions = session.sessions();
ClientSessionModel clientSession = sessions.createClientSession(realm, client);
clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL);
clientSession.setNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
UserSessionModel userSession = sessions.createUserSession(realm, clientUser, clientUsername, clientConnection.getRemoteAddr(), ServiceAccountConstants.CLIENT_AUTH, false, null, null);
event.session(userSession);
TokenManager.attachClientSession(userSession, clientSession);
// Notes about client details
userSession.setNote(ServiceAccountConstants.CLIENT_ID, client.getClientId());
userSession.setNote(ServiceAccountConstants.CLIENT_HOST, clientConnection.getRemoteHost());
userSession.setNote(ServiceAccountConstants.CLIENT_ADDRESS, clientConnection.getRemoteAddr());
updateUserSessionFromClientAuth(userSession);
AccessTokenResponse res = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession)
.generateAccessToken(session, scope, client, clientUser, userSession, clientSession)
.generateRefreshToken()
.generateIDToken()
.build();
event.success();
return Cors.add(request, Response.ok(res, MediaType.APPLICATION_JSON_TYPE)).auth().allowedOrigins(client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
}
}

View file

@ -1,5 +1,7 @@
package org.keycloak.protocol.oidc.utils;
import java.util.Map;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.events.EventBuilder;
@ -17,7 +19,7 @@ import javax.ws.rs.core.Response;
*/
public class AuthorizeClientUtil {
public static ClientModel authorizeClient(KeycloakSession session, EventBuilder event, RealmModel realm) {
public static ClientAuthResult authorizeClient(KeycloakSession session, EventBuilder event, RealmModel realm) {
AuthenticationFlowModel clientAuthFlow = realm.getClientAuthenticationFlow();
String flowId = clientAuthFlow.getId();
@ -40,7 +42,26 @@ public class AuthorizeClientUtil {
throw new ErrorResponseException("invalid_client", "Client authentication was successful, but client is null", Response.Status.BAD_REQUEST);
}
return client;
return new ClientAuthResult(client, processor.getClientAuthAttributes());
}
public static class ClientAuthResult {
private final ClientModel client;
private final Map<String, String> clientAuthAttributes;
private ClientAuthResult(ClientModel client, Map<String, String> clientAuthAttributes) {
this.client = client;
this.clientAuthAttributes = clientAuthAttributes;
}
public ClientModel getClient() {
return client;
}
public Map<String, String> getClientAuthAttributes() {
return clientAuthAttributes;
}
}
}

View file

@ -153,7 +153,7 @@ public class ClientsManagementService {
}
protected ClientModel authorizeClient() {
ClientModel client = AuthorizeClientUtil.authorizeClient(session, event, realm);
ClientModel client = AuthorizeClientUtil.authorizeClient(session, event, realm).getClient();
if (client.isPublicClient()) {
Map<String, String> error = new HashMap<String, String>();

View file

@ -1,5 +1,6 @@
package org.keycloak.testsuite.oauth;
import java.security.PrivateKey;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
@ -17,7 +18,7 @@ import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.ClientAuthAdapterUtils;
import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
import org.keycloak.constants.ServiceAccountConstants;
import org.keycloak.constants.ServiceUrlConstants;
@ -238,8 +239,8 @@ public class ClientAuthSignedJWTTest {
@Test
public void testAssertionMissingIssuer() throws Exception {
String invalidJwt = ClientAuthAdapterUtils.createSignedJWT(null, getRealmInfoUrl(),
"classpath:client-auth-test/keystore-client1.jks", "storepass", "keypass", "clientkey", KeystoreUtil.KeystoreFormat.JKS, 10);
String invalidJwt = getClientSignedJWT(
"classpath:client-auth-test/keystore-client1.jks", "storepass", "keypass", "clientkey", KeystoreUtil.KeystoreFormat.JKS, null);
List<NameValuePair> parameters = new LinkedList<NameValuePair>();
parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS));
@ -254,8 +255,8 @@ public class ClientAuthSignedJWTTest {
@Test
public void testAssertionUnknownClient() throws Exception {
String invalidJwt = ClientAuthAdapterUtils.createSignedJWT("unknown-client", getRealmInfoUrl(),
"classpath:client-auth-test/keystore-client1.jks", "storepass", "keypass", "clientkey", KeystoreUtil.KeystoreFormat.JKS, 10);
String invalidJwt = getClientSignedJWT(
"classpath:client-auth-test/keystore-client1.jks", "storepass", "keypass", "clientkey", KeystoreUtil.KeystoreFormat.JKS, "unknown-client");
List<NameValuePair> parameters = new LinkedList<NameValuePair>();
parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS));
@ -339,24 +340,8 @@ public class ClientAuthSignedJWTTest {
@Test
public void testAssertionInvalidSignature() throws Exception {
// JWT for client1, but signed by privateKey of client2
String invalidJwt = ClientAuthAdapterUtils.createSignedJWT("client1", getRealmInfoUrl(),
"classpath:client-auth-test/keystore-client2.jks", "storepass", "keypass", "clientkey", KeystoreUtil.KeystoreFormat.JKS, 10);
List<NameValuePair> parameters = new LinkedList<NameValuePair>();
parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS));
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT));
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION, invalidJwt));
HttpResponse resp = sendRequest(oauth.getServiceAccountUrl(), parameters);
OAuthClient.AccessTokenResponse response = new OAuthClient.AccessTokenResponse(resp);
assertError(response, "client1", "unauthorized_client", Errors.INVALID_CLIENT_CREDENTIALS);
}
@Test
public void testAssertionInvalidAudience() throws Exception {
String invalidJwt = ClientAuthAdapterUtils.createSignedJWT("client1", "invalid-audience",
"classpath:client-auth-test/keystore-client1.jks", "storepass", "keypass", "clientkey", KeystoreUtil.KeystoreFormat.JKS, 10);
String invalidJwt = getClientSignedJWT(
"classpath:client-auth-test/keystore-client2.jks", "storepass", "keypass", "clientkey", KeystoreUtil.KeystoreFormat.JKS, "client1");
List<NameValuePair> parameters = new LinkedList<NameValuePair>();
parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS));
@ -491,17 +476,20 @@ public class ClientAuthSignedJWTTest {
private String getClient1SignedJWT() {
return ClientAuthAdapterUtils.createSignedJWT("client1", getRealmInfoUrl(),
"classpath:client-auth-test/keystore-client1.jks", "storepass", "keypass", "clientkey", KeystoreUtil.KeystoreFormat.JKS, 10);
return getClientSignedJWT("classpath:client-auth-test/keystore-client1.jks", "storepass", "keypass", "clientkey", KeystoreUtil.KeystoreFormat.JKS, "client1");
}
private String getClient2SignedJWT() {
// keystore-client2.p12 doesn't work on Sun JDK due to restrictions on key length
// String keystoreFile = "classpath:client-auth-test/keystore-client2.p12";
return getClientSignedJWT("classpath:client-auth-test/keystore-client2.jks", "storepass", "keypass", "clientkey", KeystoreUtil.KeystoreFormat.JKS, "client2");
}
String keystoreFile = "classpath:client-auth-test/keystore-client2.jks";
return ClientAuthAdapterUtils.createSignedJWT("client2", getRealmInfoUrl(),
keystoreFile, "storepass", "keypass", "clientkey", KeystoreUtil.KeystoreFormat.JKS, 10);
private String getClientSignedJWT(String keystoreFile, String storePassword, String keyPassword, String keyAlias, KeystoreUtil.KeystoreFormat format, String clientId) {
PrivateKey privateKey = KeystoreUtil.loadPrivateKeyFromKeystore(keystoreFile, storePassword, keyPassword, keyAlias, format);
JWTClientCredentialsProvider jwtProvider = new JWTClientCredentialsProvider();
jwtProvider.setPrivateKey(privateKey);
jwtProvider.setTokenTimeout(10);
return jwtProvider.createSignedRequestToken(clientId, getRealmInfoUrl());
}
private String getRealmInfoUrl() {

View file

@ -126,7 +126,9 @@
"redirectUris": [
"http://localhost:8081/secure-portal/*"
],
"secret": "password"
"attributes": {
"jwt.credential.certificate": "MIICnTCCAYUCBgFPPLDaTzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE3MjI0N1oXDTI1MDgxNzE3MjQyN1owEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIUjjgv+V3s96O+Za9002Lp/trtGuHBeaeVL9dFKMKzO2MPqdRmHB4PqNlDdd28Rwf5Xn6iWdFpyUKOnI/yXDLhdcuFpR0sMNK/C9Lt+hSpPFLuzDqgtPgDotlMxiHIWDOZ7g9/gPYNXbNvjv8nSiyqoguoCQiiafW90bPHsiVLdP7ZIUwCcfi1qQm7FhxRJ1NiW5dvUkuCnnWEf0XR+Wzc5eC9EgB0taLFiPsSEIlWMm5xlahYyXkPdNOqZjiRnrTWm5Y4uk8ZcsD/KbPTf/7t7cQXipVaswgjdYi1kK2/zRwOhg1QwWFX/qmvdd+fLxV0R6VqRDhn7Qep2cxwMxLsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAKE6OA46sf20bz8LZPoiNsqRwBUDkaMGXfnob7s/hJZIIwDEx0IAQ3uKsG7q9wb+aA6s+v7S340zb2k3IxuhFaHaZpAd4CyR5cn1FHylbzoZ7rI/3ASqHDqpljdJaFqPH+m7nZWtyDvtZf+gkZ8OjsndwsSBK1d/jMZPp29qYbl1+XfO7RCp/jDqro/R3saYFaIFiEZPeKn1hUJn6BO48vxH1xspSu9FmlvDOEAOz4AuM58z4zRMP49GcFdCWr1wkonJUHaSptJaQwmBwLFUkCbE5I1ixGMb7mjEud6Y5jhfzJiZMo2U8RfcjNbrN0diZl3jB6LQIwESnhYSghaTjNQ=="
}
},
{
"name": "session-portal",

View file

@ -5,6 +5,13 @@
"auth-server-url" : "http://localhost:8081/auth",
"ssl-required" : "external",
"credentials" : {
"secret": "password"
}
"jwt": {
"client-keystore-file": "classpath:adapter-test/secure-portal-keystore.jks",
"client-keystore-type": "JKS",
"client-keystore-password": "storepass",
"client-key-password": "keypass",
"client-key-alias": "clientkey",
"token-expiration": 10
}
}
}

View file

@ -5,6 +5,13 @@
"auth-server-url" : "http://localhost:8080/auth",
"ssl-required" : "external",
"credentials" : {
"secret": "password"
}
"jwt": {
"client-keystore-file": "classpath:adapter-test/secure-portal-keystore.jks",
"client-keystore-type": "JKS",
"client-keystore-password": "storepass",
"client-key-password": "keypass",
"client-key-alias": "clientkey",
"token-expiration": 10
}
}
}