keycloak-aplcache
Changes
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java 5(+5 -0)
Details
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
index 8664800..ba7bc5d 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
@@ -85,6 +85,9 @@ public class KeycloakDeployment {
protected int publicKeyCacheTtl;
private PolicyEnforcer policyEnforcer;
+ // https://tools.ietf.org/html/rfc7636
+ protected boolean pkce = false;
+
public KeycloakDeployment() {
}
@@ -414,4 +417,14 @@ public class KeycloakDeployment {
public PolicyEnforcer getPolicyEnforcer() {
return policyEnforcer;
}
+
+ // https://tools.ietf.org/html/rfc7636
+ public boolean isPkce() {
+ return pkce;
+ }
+
+ public void setPkce(boolean pkce) {
+ this.pkce = pkce;
+ }
+
}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
index 65e9456..2fd9276 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
@@ -98,6 +98,11 @@ public class KeycloakDeploymentBuilder {
deployment.setCorsAllowedMethods(adapterConfig.getCorsAllowedMethods());
}
+ // https://tools.ietf.org/html/rfc7636
+ if (adapterConfig.isPkce()) {
+ deployment.setPkce(true);
+ }
+
deployment.setBearerOnly(adapterConfig.isBearerOnly());
deployment.setAutodetectBearerOnly(adapterConfig.isAutodetectBearerOnly());
deployment.setEnableBasicAuth(adapterConfig.isEnableBasicAuth());
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java
index 7ec546c..f5bfad0 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java
@@ -33,6 +33,8 @@ import org.keycloak.constants.AdapterConstants;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.util.JsonSerialization;
+import org.jboss.logging.Logger;
+
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -46,6 +48,8 @@ import java.util.List;
*/
public class ServerRequest {
+ private static Logger logger = Logger.getLogger(ServerRequest.class);
+
public static class HttpFailure extends Exception {
private int status;
private String error;
@@ -136,6 +140,62 @@ public class ServerRequest {
}
}
+ // https://tools.ietf.org/html/rfc7636#section-4
+ public static AccessTokenResponse invokeAccessCodeToToken(KeycloakDeployment deployment, String code, String redirectUri, String sessionId, String codeVerifier) 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));
+ formparams.add(new BasicNameValuePair(OAuth2Constants.REDIRECT_URI, redirectUri));
+ if (sessionId != null) {
+ formparams.add(new BasicNameValuePair(AdapterConstants.CLIENT_SESSION_STATE, sessionId));
+ formparams.add(new BasicNameValuePair(AdapterConstants.CLIENT_SESSION_HOST, HostUtils.getHostName()));
+ }
+ // https://tools.ietf.org/html/rfc7636#section-4
+ if (codeVerifier != null) {
+ logger.debugf("add to POST parameters of Token Request, codeVerifier = %s", codeVerifier);
+ formparams.add(new BasicNameValuePair(OAuth2Constants.CODE_VERIFIER, codeVerifier));
+ } else {
+ logger.debug("add to POST parameters of Token Request without codeVerifier");
+ }
+
+ HttpPost post = new HttpPost(deployment.getTokenUrl());
+ ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
+
+ UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
+ post.setEntity(form);
+ HttpResponse response = deployment.getClient().execute(post);
+ int status = response.getStatusLine().getStatusCode();
+ HttpEntity entity = response.getEntity();
+ if (status != 200) {
+ error(status, entity);
+ }
+ if (entity == null) {
+ throw new HttpFailure(status, null);
+ }
+ InputStream is = entity.getContent();
+ try {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ int c;
+ while ((c = is.read()) != -1) {
+ os.write(c);
+ }
+ byte[] bytes = os.toByteArray();
+ String json = new String(bytes);
+ try {
+ return JsonSerialization.readValue(json, AccessTokenResponse.class);
+ } catch (IOException e) {
+ throw new IOException(json, e);
+ }
+ } finally {
+ try {
+ is.close();
+ } catch (IOException ignored) {
+
+ }
+ }
+ }
+
public static AccessTokenResponse invokeRefresh(KeycloakDeployment deployment, String refreshToken) throws IOException, HttpFailure {
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN));
diff --git a/adapters/oidc/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java b/adapters/oidc/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
index 9e4fa0a..67c9f08 100755
--- a/adapters/oidc/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
+++ b/adapters/oidc/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
@@ -41,12 +41,60 @@ import java.io.InputStream;
import java.net.URI;
import java.util.List;
+import org.jboss.logging.Logger;
+import org.keycloak.common.util.Base64Url;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
+ // https://tools.ietf.org/html/rfc7636#section-4
+ private String codeVerifier;
+ private String codeChallenge;
+ private String codeChallengeMethod = OAuth2Constants.PKCE_METHOD_S256;
+ private static Logger logger = Logger.getLogger(ServletOAuthClient.class);
+
+ public static String generateSecret() {
+ return generateSecret(32);
+ }
+
+ public static String generateSecret(int bytes) {
+ byte[] buf = new byte[bytes];
+ new SecureRandom().nextBytes(buf);
+ return Base64Url.encode(buf);
+ }
+
+ private void setCodeVerifier() {
+ codeVerifier = generateSecret();
+ logger.debugf("Generated codeVerifier = %s", codeVerifier);
+ return;
+ }
+
+ private void setCodeChallenge() {
+ try {
+ if (codeChallengeMethod.equals(OAuth2Constants.PKCE_METHOD_S256)) {
+ MessageDigest md = MessageDigest.getInstance("SHA-256");
+ md.update(codeVerifier.getBytes());
+ StringBuilder sb = new StringBuilder();
+ for (byte b : md.digest()) {
+ String hex = String.format("%02x", b);
+ sb.append(hex);
+ }
+ codeChallenge = Base64Url.encode(sb.toString().getBytes());
+ } else {
+ codeChallenge = Base64Url.encode(codeVerifier.getBytes());
+ }
+ logger.debugf("Encode codeChallenge = %s, codeChallengeMethod = %s", codeChallenge, codeChallengeMethod);
+ } catch (Exception e) {
+ logger.info("PKCE client side unknown hash algorithm");
+ codeChallenge = Base64Url.encode(codeVerifier.getBytes());
+ }
+ }
+
/**
* closes client
*/
@@ -57,7 +105,15 @@ 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
KeycloakDeployment resolvedDeployment = resolveDeployment(getDeployment(), request);
- return ServerRequest.invokeAccessCodeToToken(resolvedDeployment, code, redirectUri, null);
+
+ // https://tools.ietf.org/html/rfc7636#section-4
+ if (codeVerifier != null) {
+ logger.debugf("Before sending Token Request, codeVerifier = %s", codeVerifier);
+ return ServerRequest.invokeAccessCodeToToken(resolvedDeployment, code, redirectUri, null, codeVerifier);
+ } else {
+ logger.debug("Before sending Token Request without codeVerifier");
+ return ServerRequest.invokeAccessCodeToToken(resolvedDeployment, code, redirectUri, null);
+ }
}
/**
@@ -94,6 +150,12 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
String authUrl = resolvedDeployment.getAuthUrl().clone().build().toString();
String scopeParam = TokenUtil.attachOIDCScope(scope);
+ // https://tools.ietf.org/html/rfc7636#section-4
+ if (resolvedDeployment.isPkce()) {
+ setCodeVerifier();
+ setCodeChallenge();
+ }
+
KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(authUrl)
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
.queryParam(OAuth2Constants.CLIENT_ID, getClientId())
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
index 0a107bb..f063962 100755
--- a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
@@ -78,6 +78,9 @@ public class AdapterConfig extends BaseAdapterConfig implements AdapterHttpClien
protected int publicKeyCacheTtl = 86400; // 1 day
@JsonProperty("policy-enforcer")
protected PolicyEnforcerConfig policyEnforcerConfig;
+ // https://tools.ietf.org/html/rfc7636
+ @JsonProperty("enable-pkce")
+ protected boolean pkce = false;
/**
* The Proxy url to use for requests to the auth-server, configurable via the adapter config property {@code proxy-url}.
@@ -244,4 +247,14 @@ public class AdapterConfig extends BaseAdapterConfig implements AdapterHttpClien
public void setPublicKeyCacheTtl(int publicKeyCacheTtl) {
this.publicKeyCacheTtl = publicKeyCacheTtl;
}
+
+ // https://tools.ietf.org/html/rfc7636
+ public boolean isPkce() {
+ return pkce;
+ }
+
+ public void setPkce(boolean pkce) {
+ this.pkce = pkce;
+ }
+
}
diff --git a/examples/demo-template/third-party/src/main/webapp/WEB-INF/keycloak.json b/examples/demo-template/third-party/src/main/webapp/WEB-INF/keycloak.json
index 559df05..9f07093 100755
--- a/examples/demo-template/third-party/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/demo-template/third-party/src/main/webapp/WEB-INF/keycloak.json
@@ -5,5 +5,6 @@
"ssl-required" : "external",
"credentials" : {
"secret": "password"
- }
+ },
+ "enable-pkce" : true
}
\ No newline at end of file