KEYCLOAK-1295 Adapter support. Fixes
This commit is contained in:
parent
87f7ec5909
commit
d8d6348f67
64 changed files with 915 additions and 661 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
|
Binary file not shown.
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) { %>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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": {
|
||||
|
|
|
@ -41,7 +41,6 @@ public class AppContextListener implements ServletContextListener {
|
|||
}
|
||||
ServletOAuthClientBuilder.build(is, oauthClient);
|
||||
logger.info("OAuth client configured and started");
|
||||
oauthClient.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -27,8 +27,6 @@ public class Bootstrap implements ServletContextListener {
|
|||
ServletContext context = sce.getServletContext();
|
||||
|
||||
configureClient(context);
|
||||
|
||||
client.start();
|
||||
context.setAttribute(ServletOAuthClient.class.getName(), client);
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
org.keycloak.adapters.authentication.ClientIdAndSecretCredentialsProvider
|
||||
org.keycloak.adapters.authentication.JWTClientCredentialsProvider
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
|
|
Binary file not shown.
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue