KEYCLOAK-1295 Fixes and javadoc
This commit is contained in:
parent
c9afe8fcc4
commit
b0e2624343
12 changed files with 185 additions and 23 deletions
|
@ -74,18 +74,18 @@ module.controller('ClientSignedJWTCtrl', function($scope, $location, realm, clie
|
|||
$scope.realm = realm;
|
||||
$scope.client = client;
|
||||
|
||||
var signingKeyInfo = ClientCertificate.get({ realm : realm.realm, client : client.id, attribute: 'jwt.credentials' },
|
||||
var signingKeyInfo = ClientCertificate.get({ realm : realm.realm, client : client.id, attribute: 'jwt.credential' },
|
||||
function() {
|
||||
$scope.signingKeyInfo = signingKeyInfo;
|
||||
}
|
||||
);
|
||||
|
||||
$scope.importCertificate = function() {
|
||||
$location.url("/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-jwt/Signing/import/jwt.credentials");
|
||||
$location.url("/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-jwt/Signing/import/jwt.credential");
|
||||
};
|
||||
|
||||
$scope.generateSigningKey = function() {
|
||||
$location.url("/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-jwt/Signing/export/jwt.credentials");
|
||||
$location.url("/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-jwt/Signing/export/jwt.credential");
|
||||
};
|
||||
|
||||
$scope.cancel = function() {
|
||||
|
|
|
@ -5,15 +5,57 @@ import java.util.Map;
|
|||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
|
||||
/**
|
||||
* TODO: Javadoc
|
||||
* The simple SPI for authenticating clients/applications . It's used by adapter during all OIDC backchannel requests to Keycloak server
|
||||
* (codeToToken exchange, refresh token or backchannel logout) . You can also use it in your application during direct access grants or service account request
|
||||
* (See the service-account example from Keycloak demo for more info)
|
||||
*
|
||||
* When you implement this SPI on the adapter (application) side, you also need to implement {@link org.keycloak.authentication.ClientAuthenticator} on the server side,
|
||||
* so your server is able to authenticate client
|
||||
*
|
||||
* You must specify a file
|
||||
* META-INF/services/org.keycloak.adapters.authentication.ClientCredentialsProvider in the WAR that this class is contained in (or in the JAR that is attached to the WEB-INF/lib or as jboss module
|
||||
* if you want to share the implementation among more WARs). This file must have the fully qualified class name of all your ClientAuthenticatorFactory classes
|
||||
*
|
||||
* NOTE: The SPI is not finished and method signatures are still subject to change in future versions (for example to support usecase for
|
||||
* authentication with client certificate)
|
||||
*
|
||||
* @see ClientIdAndSecretCredentialsProvider
|
||||
* @see JWTClientCredentialsProvider
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface ClientCredentialsProvider {
|
||||
|
||||
/**
|
||||
* Return the ID of the provider. Use this ID in the keycloak.json configuration as the subelement of the "credentials" element
|
||||
*
|
||||
* For example if your provider has ID "kerberos-keytab" , use the configuration like this in keycloak.json
|
||||
*
|
||||
* "credentials": {
|
||||
*
|
||||
* "kerberos-keytab": {
|
||||
* "keytab": "/tmp/foo"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String getId();
|
||||
|
||||
/**
|
||||
* Called by adapter during deployment of your application. You can for example read configuration and init your authenticator here
|
||||
*
|
||||
* @param deployment the adapter configuration
|
||||
* @param config the configuration of your provider read from keycloak.json . For the kerberos-keytab example above, it will return map with the single key "keytab" with value "/tmp/foo"
|
||||
*/
|
||||
void init(KeycloakDeployment deployment, Object config);
|
||||
|
||||
/**
|
||||
* Called every time adapter needs to perform backchannel request
|
||||
*
|
||||
* @param deployment Fully resolved deployment
|
||||
* @param requestHeaders You should put any HTTP request headers you want to use for authentication of client. These headers will be attached to the HTTP request sent to Keycloak server
|
||||
* @param formParams You should put any request parameters you want to use for authentication of client. These parameters will be attached to the HTTP request sent to Keycloak server
|
||||
*/
|
||||
void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formParams);
|
||||
}
|
||||
|
|
|
@ -60,6 +60,9 @@ public class ClientCredentialsProviderUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method when calling backchannel request directly from your application. See service-account example from demo for more details
|
||||
*/
|
||||
public static void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formparams) {
|
||||
ClientCredentialsProvider authenticator = deployment.getClientAuthenticator();
|
||||
authenticator.setClientCredentials(deployment, requestHeaders, formparams);
|
||||
|
|
|
@ -12,7 +12,8 @@ import org.keycloak.util.KeystoreUtil;
|
|||
import org.keycloak.util.Time;
|
||||
|
||||
/**
|
||||
* Client authentication based on JWT signed by client private key
|
||||
* Client authentication based on JWT signed by client private key .
|
||||
* See <a href="https://tools.ietf.org/html/draft-jones-oauth-jwt-bearer-03">specs</a> for more details.
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
|
|
|
@ -4,7 +4,9 @@ import java.util.Map;
|
|||
|
||||
import org.keycloak.AbstractOAuthClient;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.constants.ServiceUrlConstants;
|
||||
import org.keycloak.enums.RelativeUrlsUsed;
|
||||
import org.keycloak.util.KeycloakUriBuilder;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
|
@ -43,7 +45,7 @@ public class KeycloakDeploymentDelegateOAuthClient extends AbstractOAuthClient {
|
|||
|
||||
@Override
|
||||
public String getAuthUrl() {
|
||||
return deployment.getAuthUrl().clone().build().toString();
|
||||
throw new IllegalStateException("Illegal to call this method. Use KeycloakDeployment to resolve correct deployment for this request");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -53,7 +55,7 @@ public class KeycloakDeploymentDelegateOAuthClient extends AbstractOAuthClient {
|
|||
|
||||
@Override
|
||||
public String getTokenUrl() {
|
||||
return deployment.getTokenUrl();
|
||||
throw new IllegalStateException("Illegal to call this method. Use KeycloakDeployment to resolve correct deployment for this request");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,18 +1,26 @@
|
|||
package org.keycloak.servlet;
|
||||
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
import org.keycloak.adapters.HttpFacade;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.ServerRequest;
|
||||
import org.keycloak.enums.RelativeUrlsUsed;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.util.KeycloakUriBuilder;
|
||||
import org.keycloak.util.UriUtils;
|
||||
|
||||
import javax.security.cert.X509Certificate;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -29,7 +37,8 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
|
|||
|
||||
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(getDeployment(), code, redirectUri, null);
|
||||
KeycloakDeployment resolvedDeployment = resolveDeployment(getDeployment(), request);
|
||||
return ServerRequest.invokeAccessCodeToToken(resolvedDeployment, code, redirectUri, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,10 +71,12 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
|
|||
*/
|
||||
public void redirect(String redirectUri, HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
String state = getStateCode();
|
||||
KeycloakDeployment resolvedDeployment = resolveDeployment(getDeployment(), request);
|
||||
String authUrl = resolvedDeployment.getAuthUrl().clone().build().toString();
|
||||
|
||||
KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(getUrl(request, authUrl, true))
|
||||
KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(authUrl)
|
||||
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
|
||||
.queryParam(OAuth2Constants.CLIENT_ID, clientId)
|
||||
.queryParam(OAuth2Constants.CLIENT_ID, getClientId())
|
||||
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
|
||||
.queryParam(OAuth2Constants.STATE, state);
|
||||
if (scope != null) {
|
||||
|
@ -136,7 +147,8 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
|
|||
}
|
||||
|
||||
public AccessTokenResponse refreshToken(HttpServletRequest request, String refreshToken) throws IOException, ServerRequest.HttpFailure {
|
||||
return ServerRequest.invokeRefresh(getDeployment(), refreshToken);
|
||||
KeycloakDeployment resolvedDeployment = resolveDeployment(getDeployment(), request);
|
||||
return ServerRequest.invokeRefresh(resolvedDeployment, refreshToken);
|
||||
}
|
||||
|
||||
public static IDToken extractIdToken(String idToken) {
|
||||
|
@ -149,12 +161,90 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
|
|||
}
|
||||
}
|
||||
|
||||
private String getUrl(HttpServletRequest request, String url, boolean isBrowserRequest) {
|
||||
if (relativeUrlsUsed.useRelative(isBrowserRequest)) {
|
||||
String baseUrl = UriUtils.getOrigin(request.getRequestURL().toString());
|
||||
return baseUrl + url;
|
||||
} else {
|
||||
return url;
|
||||
private KeycloakDeployment resolveDeployment(KeycloakDeployment baseDeployment, HttpServletRequest request) {
|
||||
ServletFacade facade = new ServletFacade(request);
|
||||
return new AdapterDeploymentContext(baseDeployment).resolveDeployment(facade);
|
||||
}
|
||||
|
||||
|
||||
public static class ServletFacade implements HttpFacade {
|
||||
|
||||
private final HttpServletRequest servletRequest;
|
||||
|
||||
private ServletFacade(HttpServletRequest servletRequest) {
|
||||
this.servletRequest = servletRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeycloakSecurityContext getSecurityContext() {
|
||||
throw new IllegalStateException("Not yet implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getRequest() {
|
||||
return new Request() {
|
||||
|
||||
@Override
|
||||
public String getMethod() {
|
||||
return servletRequest.getMethod();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getURI() {
|
||||
return servletRequest.getRequestURL().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure() {
|
||||
return servletRequest.isSecure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryParamValue(String param) {
|
||||
return servletRequest.getParameter(param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cookie getCookie(String cookieName) {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(String name) {
|
||||
return servletRequest.getHeader(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getHeaders(String name) {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
try {
|
||||
return servletRequest.getInputStream();
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteAddr() {
|
||||
return servletRequest.getRemoteAddr();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response getResponse() {
|
||||
throw new IllegalStateException("Not yet implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getCertificateChain() {
|
||||
throw new IllegalStateException("Not yet implemented");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,8 @@ 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());
|
||||
Assert.assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/auth", oauthClient.getDeployment().getAuthUrl().clone().build().toString());
|
||||
Assert.assertEquals("https://backend:8443/auth/realms/demo/protocol/openid-connect/token", oauthClient.getDeployment().getTokenUrl());
|
||||
assertEquals(RelativeUrlsUsed.NEVER, oauthClient.getRelativeUrlsUsed());
|
||||
Assert.assertEquals("customer-portal", oauthClient.getClientId());
|
||||
Assert.assertEquals("234234-234234-234234", oauthClient.getCredentials().get(CredentialRepresentation.SECRET));
|
||||
|
|
|
@ -5,6 +5,7 @@ import java.util.Map;
|
|||
import org.keycloak.models.ClientModel;
|
||||
|
||||
/**
|
||||
* Encapsulates information about the execution in ClientAuthenticationFlow
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
|
|
|
@ -6,12 +6,23 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.provider.Provider;
|
||||
|
||||
/**
|
||||
* This interface is for users that want to add custom client authenticators to an authentication flow.
|
||||
* You must implement this interface as well as a ClientAuthenticatorFactory.
|
||||
*
|
||||
* This interface is for verifying client credentials from request. On the adapter side, you must also implement org.keycloak.adapters.authentication.ClientCredentialsProvider , which is supposed
|
||||
* to add the client credentials to the request, which will ClientAuthenticator verify on server side
|
||||
*
|
||||
* @see org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator
|
||||
* @see org.keycloak.authentication.authenticators.client.JWTClientAuthenticator
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface ClientAuthenticator extends Provider {
|
||||
|
||||
/**
|
||||
* TODO: javadoc
|
||||
* Initial call for the authenticator. This method should check the current HTTP request to determine if the request
|
||||
* satisfies the ClientAuthenticator's requirements. If it doesn't, it should send back a challenge response by calling
|
||||
* the ClientAuthenticationFlowContext.challenge(Response).
|
||||
*
|
||||
* @param context
|
||||
*/
|
||||
|
|
|
@ -3,7 +3,11 @@ package org.keycloak.authentication;
|
|||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* Factory for creating ClientAuthenticator instances. This is a singleton and created when Keycloak boots.
|
||||
*
|
||||
* You must specify a file
|
||||
* META-INF/services/org.keycloak.authentication.ClientAuthenticatorFactory in the jar that this class is contained in
|
||||
* This file must have the fully qualified class name of all your ClientAuthenticatorFactory classes
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
|
@ -19,7 +23,7 @@ public interface ClientAuthenticatorFactory extends ProviderFactory<ClientAuthen
|
|||
boolean isConfigurable();
|
||||
|
||||
/**
|
||||
* Is this authenticator configurable per client?
|
||||
* Is this authenticator configurable per client? The configuration will be in "Clients" / "Credentials" tab in admin console
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
|
|
|
@ -22,7 +22,9 @@ import org.keycloak.provider.ProviderConfigProperty;
|
|||
import org.keycloak.util.BasicAuthHelper;
|
||||
|
||||
/**
|
||||
* Validates client based on "client_id" and "client_secret" sent either in request parameters or in "Authorization: Basic" header
|
||||
* Validates client based on "client_id" and "client_secret" sent either in request parameters or in "Authorization: Basic" header .
|
||||
*
|
||||
* See org.keycloak.adapters.authentication.ClientIdAndSecretAuthenticator for the adapter
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
|
|
|
@ -24,6 +24,12 @@ import org.keycloak.representations.JsonWebToken;
|
|||
import org.keycloak.services.Urls;
|
||||
|
||||
/**
|
||||
* Client authentication based on JWT signed by client private key .
|
||||
* See <a href="https://tools.ietf.org/html/draft-jones-oauth-jwt-bearer-03">specs</a> for more details.
|
||||
*
|
||||
* This is server side, which verifies JWT from client_assertion parameter, where the assertion was created on adapter side by
|
||||
* org.keycloak.adapters.authentication.JWTClientCredentialsProvider
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class JWTClientAuthenticator extends AbstractClientAuthenticator {
|
||||
|
|
Loading…
Reference in a new issue