You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by co...@apache.org on 2019/08/15 13:25:39 UTC

[cxf] branch master updated (6164a86 -> 563b1ec)

This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/cxf.git.


    from 6164a86  SLF4J upgrade
     new 840282a  Adding OAuth public client tests
     new 563b1ec  Adding OAuth PKCE Digest tests

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../oauth2/common/JCacheOAuthDataProviderImpl.java |  17 +-
 .../security/oauth2/common/OAuth2TestUtils.java    |  34 ++
 .../security/oauth2/grants/PublicClientTest.java   | 351 +++++++++++++++++++++
 ...ver-jcache-jwt.xml => grants-server-public.xml} |  81 +++--
 .../jaxrs/security/oauth2/grants/publicclient.xml} |   6 +-
 5 files changed, 459 insertions(+), 30 deletions(-)
 create mode 100644 systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oauth2/grants/PublicClientTest.java
 copy systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/grants/{grants-negative-server-jcache-jwt.xml => grants-server-public.xml} (70%)
 copy systests/{ws-security/src/test/resources/org/apache/cxf/systest/ws/saml/subjectconf/client-noauth.xml => rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/grants/publicclient.xml} (93%)


[cxf] 02/02: Adding OAuth PKCE Digest tests

Posted by co...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit 563b1ec1f5b2186003843d5e686cc764efa00bb3
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Thu Aug 15 14:25:18 2019 +0100

    Adding OAuth PKCE Digest tests
---
 .../security/oauth2/common/OAuth2TestUtils.java    |   2 +-
 .../security/oauth2/grants/PublicClientTest.java   | 123 +++++++++++++++++++++
 .../oauth2/grants/grants-server-public.xml         |  33 ++++++
 3 files changed, 157 insertions(+), 1 deletion(-)

diff --git a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oauth2/common/OAuth2TestUtils.java b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oauth2/common/OAuth2TestUtils.java
index 328211e..a6ddb2c 100644
--- a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oauth2/common/OAuth2TestUtils.java
+++ b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oauth2/common/OAuth2TestUtils.java
@@ -156,7 +156,7 @@ public final class OAuth2TestUtils {
                                                                         String code,
                                                                         String consumerId,
                                                                         String audience) {
-        return getAccessTokenWithAuthorizationCode(client, code, "consumer-id", audience, null);
+        return getAccessTokenWithAuthorizationCode(client, code, consumerId, audience, null);
     }
 
     public static ClientAccessToken getAccessTokenWithAuthorizationCode(WebClient client,
diff --git a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oauth2/grants/PublicClientTest.java b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oauth2/grants/PublicClientTest.java
index 150719b..606aee0 100644
--- a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oauth2/grants/PublicClientTest.java
+++ b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oauth2/grants/PublicClientTest.java
@@ -27,6 +27,8 @@ import org.apache.cxf.bus.spring.SpringBusFactory;
 import org.apache.cxf.common.util.Base64UrlUtility;
 import org.apache.cxf.jaxrs.client.WebClient;
 import org.apache.cxf.rs.security.oauth2.common.ClientAccessToken;
+import org.apache.cxf.rs.security.oauth2.grants.code.CodeVerifierTransformer;
+import org.apache.cxf.rs.security.oauth2.grants.code.DigestCodeVerifier;
 import org.apache.cxf.rt.security.crypto.CryptoUtils;
 import org.apache.cxf.systest.jaxrs.security.SecurityTestUtil;
 import org.apache.cxf.systest.jaxrs.security.oauth2.common.OAuth2TestUtils;
@@ -196,12 +198,133 @@ public class PublicClientTest extends AbstractBusClientServerTestBase {
         try {
             codeVerifier = Base64UrlUtility.encode(CryptoUtils.generateSecureRandomBytes(32));
             OAuth2TestUtils.getAccessTokenWithAuthorizationCode(client, code, "consumer-id", null, codeVerifier);
+            fail("Failure expected on a different verifier");
+        } catch (Exception ex) {
+            // expected
+        }
+    }
+
+    @org.junit.Test
+    public void testPKCEDigest() throws Exception {
+        URL busFile = PublicClientTest.class.getResource("publicclient.xml");
+
+        String address = "https://localhost:" + JCACHE_PORT + "/services/";
+        WebClient client = WebClient.create(address, OAuth2TestUtils.setupProviders(),
+                                            "alice", "security", busFile.toString());
+        // Save the Cookie for the second request...
+        WebClient.getConfig(client).getRequestContext().put(
+            org.apache.cxf.message.Message.MAINTAIN_SESSION, Boolean.TRUE);
+
+        // Get Authorization Code
+        AuthorizationCodeParameters parameters = new AuthorizationCodeParameters();
+        parameters.setConsumerId("consumer-id");
+        String codeVerifier = Base64UrlUtility.encode(CryptoUtils.generateSecureRandomBytes(32));
+        CodeVerifierTransformer transformer = new DigestCodeVerifier();
+        String codeChallenge = transformer.transformCodeVerifier(codeVerifier);
+        parameters.setCodeChallenge(codeChallenge);
+        parameters.setCodeChallengeMethod(transformer.getChallengeMethod());
+        parameters.setResponseType("code");
+        parameters.setPath("authorize/");
+
+        String location = OAuth2TestUtils.getLocation(client, parameters);
+        String code = OAuth2TestUtils.getSubstring(location, "code");
+        assertNotNull(code);
+
+        // Now get the access token - note services3 doesn't require basic auth
+        String address2 = "https://localhost:" + JCACHE_PORT + "/services3/";
+        client = WebClient.create(address2, OAuth2TestUtils.setupProviders(), busFile.toString());
+        // Save the Cookie for the second request...
+        WebClient.getConfig(client).getRequestContext().put(
+            org.apache.cxf.message.Message.MAINTAIN_SESSION, Boolean.TRUE);
+
+        ClientAccessToken accessToken =
+            OAuth2TestUtils.getAccessTokenWithAuthorizationCode(client, code, "consumer-id", null, codeVerifier);
+        assertNotNull(accessToken.getTokenKey());
+    }
+
+    @org.junit.Test
+    public void testPKCEDigestMissingVerifier() throws Exception {
+        URL busFile = PublicClientTest.class.getResource("publicclient.xml");
+
+        String address = "https://localhost:" + JCACHE_PORT + "/services/";
+        WebClient client = WebClient.create(address, OAuth2TestUtils.setupProviders(),
+                                            "alice", "security", busFile.toString());
+        // Save the Cookie for the second request...
+        WebClient.getConfig(client).getRequestContext().put(
+            org.apache.cxf.message.Message.MAINTAIN_SESSION, Boolean.TRUE);
+
+        // Get Authorization Code
+        AuthorizationCodeParameters parameters = new AuthorizationCodeParameters();
+        parameters.setConsumerId("consumer-id");
+        String codeVerifier = Base64UrlUtility.encode(CryptoUtils.generateSecureRandomBytes(32));
+        CodeVerifierTransformer transformer = new DigestCodeVerifier();
+        String codeChallenge = transformer.transformCodeVerifier(codeVerifier);
+        parameters.setCodeChallenge(codeChallenge);
+        parameters.setCodeChallengeMethod(transformer.getChallengeMethod());
+        parameters.setResponseType("code");
+        parameters.setPath("authorize/");
+
+        String location = OAuth2TestUtils.getLocation(client, parameters);
+        String code = OAuth2TestUtils.getSubstring(location, "code");
+        assertNotNull(code);
+
+        // Now get the access token - note services3 doesn't require basic auth
+        String address2 = "https://localhost:" + JCACHE_PORT + "/services3/";
+        client = WebClient.create(address2, OAuth2TestUtils.setupProviders(), busFile.toString());
+        // Save the Cookie for the second request...
+        WebClient.getConfig(client).getRequestContext().put(
+            org.apache.cxf.message.Message.MAINTAIN_SESSION, Boolean.TRUE);
+
+        try {
+            OAuth2TestUtils.getAccessTokenWithAuthorizationCode(client, code, "consumer-id", null);
             fail("Failure expected on a missing verifier");
         } catch (Exception ex) {
             // expected
         }
     }
 
+    @org.junit.Test
+    public void testPKCEDigestDifferentVerifier() throws Exception {
+        URL busFile = PublicClientTest.class.getResource("publicclient.xml");
+
+        String address = "https://localhost:" + JCACHE_PORT + "/services/";
+        WebClient client = WebClient.create(address, OAuth2TestUtils.setupProviders(),
+                                            "alice", "security", busFile.toString());
+        // Save the Cookie for the second request...
+        WebClient.getConfig(client).getRequestContext().put(
+            org.apache.cxf.message.Message.MAINTAIN_SESSION, Boolean.TRUE);
+
+        // Get Authorization Code
+        AuthorizationCodeParameters parameters = new AuthorizationCodeParameters();
+        parameters.setConsumerId("consumer-id");
+        String codeVerifier = Base64UrlUtility.encode(CryptoUtils.generateSecureRandomBytes(32));
+        CodeVerifierTransformer transformer = new DigestCodeVerifier();
+        String codeChallenge = transformer.transformCodeVerifier(codeVerifier);
+        parameters.setCodeChallenge(codeChallenge);
+        parameters.setCodeChallengeMethod(transformer.getChallengeMethod());
+        parameters.setResponseType("code");
+        parameters.setPath("authorize/");
+
+        String location = OAuth2TestUtils.getLocation(client, parameters);
+        String code = OAuth2TestUtils.getSubstring(location, "code");
+        assertNotNull(code);
+
+        // Now get the access token - note services3 doesn't require basic auth
+        String address2 = "https://localhost:" + JCACHE_PORT + "/services3/";
+        client = WebClient.create(address2, OAuth2TestUtils.setupProviders(), busFile.toString());
+        // Save the Cookie for the second request...
+        WebClient.getConfig(client).getRequestContext().put(
+            org.apache.cxf.message.Message.MAINTAIN_SESSION, Boolean.TRUE);
+
+        try {
+            codeVerifier = Base64UrlUtility.encode(CryptoUtils.generateSecureRandomBytes(32));
+            OAuth2TestUtils.getAccessTokenWithAuthorizationCode(client, code, "consumer-id", null, codeVerifier);
+            fail("Failure expected on a different verifier");
+        } catch (Exception ex) {
+            // expected
+        }
+    }
+
     //
     // Server implementations
     //
diff --git a/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/grants/grants-server-public.xml b/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/grants/grants-server-public.xml
index 905349f..32d2bba 100644
--- a/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/grants/grants-server-public.xml
+++ b/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/grants/grants-server-public.xml
@@ -145,5 +145,38 @@ under the License.
        </jaxrs:properties>
    </jaxrs:server>
    
+   <bean id="digestVerifier" class="org.apache.cxf.rs.security.oauth2.grants.code.DigestCodeVerifier" />
+   <bean id="codeGrantHandler" class="org.apache.cxf.rs.security.oauth2.grants.code.AuthorizationCodeGrantHandler">
+      <property name="dataProvider" ref="oauthProvider"/>
+      <property name="codeVerifierTransformer" ref="digestVerifier"/>
+   </bean>
+   
+   <bean id="digestTokenService" class="org.apache.cxf.rs.security.oauth2.services.AccessTokenService">
+      <property name="dataProvider" ref="oauthProvider"/>
+      <property name="canSupportPublicClients" value="true"/>
+      <property name="grantHandlers">
+         <list>
+             <ref bean="codeGrantHandler"/>
+         </list>
+      </property>
+   </bean>
+   
+   <jaxrs:server 
+       depends-on="tls-config" 
+       address="https://localhost:${testutil.ports.jaxrs-oauth2-grants-jcache-public}/services3">
+       <jaxrs:serviceBeans>
+           <ref bean="digestTokenService"/>
+       </jaxrs:serviceBeans>
+       <jaxrs:properties>
+           <entry key="security.signature.properties" 
+                  value="org/apache/cxf/systest/jaxrs/security/bob.properties"/>
+           <entry key="rs.security.keystore.type" value="jks" />
+           <entry key="rs.security.keystore.alias" value="alice"/>
+           <entry key="rs.security.keystore.password" value="password"/>
+           <entry key="rs.security.keystore.file" value="keys/alice.jks" />
+           <entry key="rs.security.signature.algorithm" value="RS256" />
+       </jaxrs:properties>
+   </jaxrs:server>
+   
 
 </beans>


[cxf] 01/02: Adding OAuth public client tests

Posted by co...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit 840282a01cb6fe4ceb1dc3382cf48f2fbfcaf1fc
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Thu Aug 15 13:34:58 2019 +0100

    Adding OAuth public client tests
---
 .../oauth2/common/JCacheOAuthDataProviderImpl.java |  17 +-
 .../security/oauth2/common/OAuth2TestUtils.java    |  34 +++
 .../security/oauth2/grants/PublicClientTest.java   | 228 +++++++++++++++++++++
 .../oauth2/grants/grants-server-public.xml         | 149 ++++++++++++++
 .../jaxrs/security/oauth2/grants/publicclient.xml  |  42 ++++
 5 files changed, 466 insertions(+), 4 deletions(-)

diff --git a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oauth2/common/JCacheOAuthDataProviderImpl.java b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oauth2/common/JCacheOAuthDataProviderImpl.java
index 8161092..378529d 100644
--- a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oauth2/common/JCacheOAuthDataProviderImpl.java
+++ b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oauth2/common/JCacheOAuthDataProviderImpl.java
@@ -56,6 +56,11 @@ public class JCacheOAuthDataProviderImpl extends JCacheCodeDataProvider {
 
     public JCacheOAuthDataProviderImpl(String servicePort, String partnerPort,
                                        boolean storeJwtTokenKeyOnly) throws Exception {
+        this(servicePort, partnerPort, storeJwtTokenKeyOnly, false);
+    }
+
+    public JCacheOAuthDataProviderImpl(String servicePort, String partnerPort,
+                                       boolean storeJwtTokenKeyOnly, boolean createPublicClients) throws Exception {
         // Create random cache files, as this provider could be called by several test implementations
         super(DEFAULT_CONFIG_URL, BusFactory.getThreadDefaultBus(true),
               CLIENT_CACHE_KEY + "_" + Math.abs(new Random().nextInt()),
@@ -64,7 +69,8 @@ public class JCacheOAuthDataProviderImpl extends JCacheCodeDataProvider {
               REFRESH_TOKEN_CACHE_KEY + "_" + Math.abs(new Random().nextInt()),
               storeJwtTokenKeyOnly);
         // filters/grants test client
-        Client client = new Client("consumer-id", "this-is-a-secret", true);
+        Client client = createPublicClients ? new Client("consumer-id", null, false)
+            : new Client("consumer-id", "this-is-a-secret", true);
         List<String> redirectUris = new ArrayList<>();
         redirectUris.add("http://www.blah.apache.org");
         if (partnerPort != null) {
@@ -92,7 +98,8 @@ public class JCacheOAuthDataProviderImpl extends JCacheCodeDataProvider {
         this.setClient(client);
 
         // OIDC filters test client
-        client = new Client("consumer-id-oidc", "this-is-a-secret", true);
+        client = createPublicClients ? new Client("consumer-id-oidc", null, false)
+            : new Client("consumer-id-oidc", "this-is-a-secret", true);
         client.setRedirectUris(Collections.singletonList("https://localhost:" + servicePort
                                                          + "/secured/bookstore/books"));
 
@@ -104,7 +111,8 @@ public class JCacheOAuthDataProviderImpl extends JCacheCodeDataProvider {
         this.setClient(client);
 
         // Audience test client
-        client = new Client("consumer-id-aud", "this-is-a-secret", true);
+        client = createPublicClients ? new Client("consumer-id-aud", null, false)
+            : new Client("consumer-id-aud", "this-is-a-secret", true);
         client.setRedirectUris(Collections.singletonList("http://www.blah.apache.org"));
 
         client.getAllowedGrantTypes().add("authorization_code");
@@ -118,7 +126,8 @@ public class JCacheOAuthDataProviderImpl extends JCacheCodeDataProvider {
         this.setClient(client);
 
         // Audience test client 2
-        client = new Client("consumer-id-aud2", "this-is-a-secret", true);
+        client = createPublicClients ? new Client("consumer-id-aud2", null, false)
+            : new Client("consumer-id-aud2", "this-is-a-secret", true);
         client.setRedirectUris(Collections.singletonList("http://www.blah.apache.org"));
 
         client.getAllowedGrantTypes().add("authorization_code");
diff --git a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oauth2/common/OAuth2TestUtils.java b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oauth2/common/OAuth2TestUtils.java
index 336381b..328211e 100644
--- a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oauth2/common/OAuth2TestUtils.java
+++ b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oauth2/common/OAuth2TestUtils.java
@@ -100,6 +100,12 @@ public final class OAuth2TestUtils {
         if (parameters.getRequest() != null) {
             client.query("request", parameters.getRequest());
         }
+        if (parameters.getCodeChallenge() != null) {
+            client.query("code_challenge", parameters.getCodeChallenge());
+        }
+        if (parameters.getCodeChallengeMethod() != null) {
+            client.query("code_challenge_method", parameters.getCodeChallengeMethod());
+        }
 
         client.path(parameters.getPath());
         Response response = client.get();
@@ -127,6 +133,9 @@ public final class OAuth2TestUtils {
         if (authzData.getState() != null) {
             form.param("state", authzData.getState());
         }
+        if (authzData.getClientCodeChallenge() != null) {
+            form.param("code_challenge", authzData.getClientCodeChallenge());
+        }
         form.param("response_type", authzData.getResponseType());
         form.param("oauthDecision", "allow");
 
@@ -147,6 +156,14 @@ public final class OAuth2TestUtils {
                                                                         String code,
                                                                         String consumerId,
                                                                         String audience) {
+        return getAccessTokenWithAuthorizationCode(client, code, "consumer-id", audience, null);
+    }
+
+    public static ClientAccessToken getAccessTokenWithAuthorizationCode(WebClient client,
+                                                                        String code,
+                                                                        String consumerId,
+                                                                        String audience,
+                                                                        String codeVerifier) {
         client.type("application/x-www-form-urlencoded").accept("application/json");
         client.path("token");
 
@@ -157,6 +174,9 @@ public final class OAuth2TestUtils {
         if (audience != null) {
             form.param("audience", audience);
         }
+        if (codeVerifier != null) {
+            form.param("code_verifier", codeVerifier);
+        }
         form.param("redirect_uri", "http://www.blah.apache.org");
         Response response = client.post(form);
 
@@ -264,6 +284,8 @@ public final class OAuth2TestUtils {
         private String responseType;
         private String path;
         private String request;
+        private String codeChallenge;
+        private String codeChallengeMethod;
 
         public String getScope() {
             return scope;
@@ -307,5 +329,17 @@ public final class OAuth2TestUtils {
         public void setRequest(String request) {
             this.request = request;
         }
+        public String getCodeChallenge() {
+            return codeChallenge;
+        }
+        public void setCodeChallenge(String codeChallenge) {
+            this.codeChallenge = codeChallenge;
+        }
+        public String getCodeChallengeMethod() {
+            return codeChallengeMethod;
+        }
+        public void setCodeChallengeMethod(String codeChallengeMethod) {
+            this.codeChallengeMethod = codeChallengeMethod;
+        }
     }
 }
\ No newline at end of file
diff --git a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oauth2/grants/PublicClientTest.java b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oauth2/grants/PublicClientTest.java
new file mode 100644
index 0000000..150719b
--- /dev/null
+++ b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oauth2/grants/PublicClientTest.java
@@ -0,0 +1,228 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.cxf.systest.jaxrs.security.oauth2.grants;
+
+import java.net.URL;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.BusFactory;
+import org.apache.cxf.bus.spring.SpringBusFactory;
+import org.apache.cxf.common.util.Base64UrlUtility;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.cxf.rs.security.oauth2.common.ClientAccessToken;
+import org.apache.cxf.rt.security.crypto.CryptoUtils;
+import org.apache.cxf.systest.jaxrs.security.SecurityTestUtil;
+import org.apache.cxf.systest.jaxrs.security.oauth2.common.OAuth2TestUtils;
+import org.apache.cxf.systest.jaxrs.security.oauth2.common.OAuth2TestUtils.AuthorizationCodeParameters;
+import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
+import org.apache.cxf.testutil.common.AbstractBusTestServerBase;
+import org.apache.cxf.testutil.common.TestUtil;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Some tests for public clients.
+ */
+public class PublicClientTest extends AbstractBusClientServerTestBase {
+    public static final String JCACHE_PORT = TestUtil.getPortNumber("jaxrs-oauth2-grants-jcache-public");
+    public static final String JCACHE_PORT2 = TestUtil.getPortNumber("jaxrs-oauth2-grants2-jcache-public");
+
+    @BeforeClass
+    public static void startServers() throws Exception {
+        assertTrue("server did not launch correctly",
+                   launchServer(BookServerOAuth2GrantsJCache.class, true));
+    }
+
+    @AfterClass
+    public static void cleanup() throws Exception {
+        SecurityTestUtil.cleanup();
+    }
+
+    @org.junit.Test
+    public void testAuthorizationCodeGrant() throws Exception {
+        URL busFile = PublicClientTest.class.getResource("publicclient.xml");
+
+        String address = "https://localhost:" + JCACHE_PORT + "/services/";
+        WebClient client = WebClient.create(address, OAuth2TestUtils.setupProviders(),
+                                            "alice", "security", busFile.toString());
+        // Save the Cookie for the second request...
+        WebClient.getConfig(client).getRequestContext().put(
+            org.apache.cxf.message.Message.MAINTAIN_SESSION, Boolean.TRUE);
+
+        // Get Authorization Code
+        String code = OAuth2TestUtils.getAuthorizationCode(client);
+        assertNotNull(code);
+
+        // Now get the access token - note services2 doesn't require basic auth
+        String address2 = "https://localhost:" + JCACHE_PORT + "/services2/";
+        client = WebClient.create(address2, OAuth2TestUtils.setupProviders(), busFile.toString());
+        // Save the Cookie for the second request...
+        WebClient.getConfig(client).getRequestContext().put(
+            org.apache.cxf.message.Message.MAINTAIN_SESSION, Boolean.TRUE);
+
+        ClientAccessToken accessToken =
+            OAuth2TestUtils.getAccessTokenWithAuthorizationCode(client, code);
+        assertNotNull(accessToken.getTokenKey());
+    }
+
+    @org.junit.Test
+    public void testPKCEPlain() throws Exception {
+        URL busFile = PublicClientTest.class.getResource("publicclient.xml");
+
+        String address = "https://localhost:" + JCACHE_PORT + "/services/";
+        WebClient client = WebClient.create(address, OAuth2TestUtils.setupProviders(),
+                                            "alice", "security", busFile.toString());
+        // Save the Cookie for the second request...
+        WebClient.getConfig(client).getRequestContext().put(
+            org.apache.cxf.message.Message.MAINTAIN_SESSION, Boolean.TRUE);
+
+        // Get Authorization Code
+        AuthorizationCodeParameters parameters = new AuthorizationCodeParameters();
+        parameters.setConsumerId("consumer-id");
+        String codeVerifier = Base64UrlUtility.encode(CryptoUtils.generateSecureRandomBytes(32));
+        parameters.setCodeChallenge(codeVerifier);
+        parameters.setCodeChallengeMethod("plain");
+        parameters.setResponseType("code");
+        parameters.setPath("authorize/");
+
+        String location = OAuth2TestUtils.getLocation(client, parameters);
+        String code = OAuth2TestUtils.getSubstring(location, "code");
+        assertNotNull(code);
+
+        // Now get the access token - note services2 doesn't require basic auth
+        String address2 = "https://localhost:" + JCACHE_PORT + "/services2/";
+        client = WebClient.create(address2, OAuth2TestUtils.setupProviders(), busFile.toString());
+        // Save the Cookie for the second request...
+        WebClient.getConfig(client).getRequestContext().put(
+            org.apache.cxf.message.Message.MAINTAIN_SESSION, Boolean.TRUE);
+
+        ClientAccessToken accessToken =
+            OAuth2TestUtils.getAccessTokenWithAuthorizationCode(client, code, "consumer-id", null, codeVerifier);
+        assertNotNull(accessToken.getTokenKey());
+    }
+
+    @org.junit.Test
+    public void testPKCEPlainMissingVerifier() throws Exception {
+        URL busFile = PublicClientTest.class.getResource("publicclient.xml");
+
+        String address = "https://localhost:" + JCACHE_PORT + "/services/";
+        WebClient client = WebClient.create(address, OAuth2TestUtils.setupProviders(),
+                                            "alice", "security", busFile.toString());
+        // Save the Cookie for the second request...
+        WebClient.getConfig(client).getRequestContext().put(
+            org.apache.cxf.message.Message.MAINTAIN_SESSION, Boolean.TRUE);
+
+        // Get Authorization Code
+        AuthorizationCodeParameters parameters = new AuthorizationCodeParameters();
+        parameters.setConsumerId("consumer-id");
+        String codeVerifier = Base64UrlUtility.encode(CryptoUtils.generateSecureRandomBytes(32));
+        parameters.setCodeChallenge(codeVerifier);
+        parameters.setCodeChallengeMethod("plain");
+        parameters.setResponseType("code");
+        parameters.setPath("authorize/");
+
+        String location = OAuth2TestUtils.getLocation(client, parameters);
+        String code = OAuth2TestUtils.getSubstring(location, "code");
+        assertNotNull(code);
+
+        // Now get the access token - note services2 doesn't require basic auth
+        String address2 = "https://localhost:" + JCACHE_PORT + "/services2/";
+        client = WebClient.create(address2, OAuth2TestUtils.setupProviders(), busFile.toString());
+        // Save the Cookie for the second request...
+        WebClient.getConfig(client).getRequestContext().put(
+            org.apache.cxf.message.Message.MAINTAIN_SESSION, Boolean.TRUE);
+
+        try {
+            OAuth2TestUtils.getAccessTokenWithAuthorizationCode(client, code, "consumer-id", null);
+            fail("Failure expected on a missing verifier");
+        } catch (Exception ex) {
+            // expected
+        }
+    }
+
+    @org.junit.Test
+    public void testPKCEPlainDifferentVerifier() throws Exception {
+        URL busFile = PublicClientTest.class.getResource("publicclient.xml");
+
+        String address = "https://localhost:" + JCACHE_PORT + "/services/";
+        WebClient client = WebClient.create(address, OAuth2TestUtils.setupProviders(),
+                                            "alice", "security", busFile.toString());
+        // Save the Cookie for the second request...
+        WebClient.getConfig(client).getRequestContext().put(
+            org.apache.cxf.message.Message.MAINTAIN_SESSION, Boolean.TRUE);
+
+        // Get Authorization Code
+        AuthorizationCodeParameters parameters = new AuthorizationCodeParameters();
+        parameters.setConsumerId("consumer-id");
+        String codeVerifier = Base64UrlUtility.encode(CryptoUtils.generateSecureRandomBytes(32));
+        parameters.setCodeChallenge(codeVerifier);
+        parameters.setCodeChallengeMethod("plain");
+        parameters.setResponseType("code");
+        parameters.setPath("authorize/");
+
+        String location = OAuth2TestUtils.getLocation(client, parameters);
+        String code = OAuth2TestUtils.getSubstring(location, "code");
+        assertNotNull(code);
+
+        // Now get the access token - note services2 doesn't require basic auth
+        String address2 = "https://localhost:" + JCACHE_PORT + "/services2/";
+        client = WebClient.create(address2, OAuth2TestUtils.setupProviders(), busFile.toString());
+        // Save the Cookie for the second request...
+        WebClient.getConfig(client).getRequestContext().put(
+            org.apache.cxf.message.Message.MAINTAIN_SESSION, Boolean.TRUE);
+
+        try {
+            codeVerifier = Base64UrlUtility.encode(CryptoUtils.generateSecureRandomBytes(32));
+            OAuth2TestUtils.getAccessTokenWithAuthorizationCode(client, code, "consumer-id", null, codeVerifier);
+            fail("Failure expected on a missing verifier");
+        } catch (Exception ex) {
+            // expected
+        }
+    }
+
+    //
+    // Server implementations
+    //
+
+    public static class BookServerOAuth2GrantsJCache extends AbstractBusTestServerBase {
+        private static final URL SERVER_CONFIG_FILE =
+            BookServerOAuth2GrantsJCache.class.getResource("grants-server-public.xml");
+
+        protected void run() {
+            SpringBusFactory bf = new SpringBusFactory();
+            Bus springBus = bf.createBus(SERVER_CONFIG_FILE);
+            BusFactory.setDefaultBus(springBus);
+            setBus(springBus);
+
+            try {
+                new BookServerOAuth2GrantsJCache();
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+    }
+
+}
diff --git a/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/grants/grants-server-public.xml b/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/grants/grants-server-public.xml
new file mode 100644
index 0000000..905349f
--- /dev/null
+++ b/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/grants/grants-server-public.xml
@@ -0,0 +1,149 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans" 
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
+    xmlns:http="http://cxf.apache.org/transports/http/configuration" 
+    xmlns:httpj="http://cxf.apache.org/transports/http-jetty/configuration" 
+    xmlns:sec="http://cxf.apache.org/configuration/security" 
+    xmlns:cxf="http://cxf.apache.org/core" 
+    xmlns:jaxrs="http://cxf.apache.org/jaxrs" 
+    xmlns:util="http://www.springframework.org/schema/util"
+    xsi:schemaLocation="http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd
+             http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd
+             http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+             http://www.springframework.org/schema/util  http://www.springframework.org/schema/util/spring-util-4.2.xsd
+             http://cxf.apache.org/transports/http/configuration http://cxf.apache.org/schemas/configuration/http-conf.xsd
+             http://cxf.apache.org/transports/http-jetty/configuration http://cxf.apache.org/schemas/configuration/http-jetty.xsd 
+             http://cxf.apache.org/configuration/security http://cxf.apache.org/schemas/configuration/security.xsd">
+    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/>
+    <cxf:bus>
+        <cxf:features>
+            <cxf:logging/>
+        </cxf:features>
+        <cxf:properties> 
+          <entry key="org.apache.cxf.jaxrs.bus.providers" value-ref="busProviders"/> 
+        </cxf:properties>
+    </cxf:bus>
+    <!-- providers -->
+    <util:list id="busProviders"> 
+        <ref bean="oauthJson"/> 
+    </util:list> 
+    <bean id="oauthJson" class="org.apache.cxf.rs.security.oauth2.provider.OAuthJSONProvider"/>
+    
+    <httpj:engine-factory id="tls-config">
+        <httpj:engine port="${testutil.ports.jaxrs-oauth2-grants-jcache-public}">
+            <httpj:tlsServerParameters>
+                <sec:keyManagers keyPassword="password">
+                    <sec:keyStore type="JKS" password="password" resource="keys/Bethal.jks"/>
+                </sec:keyManagers>
+                <sec:trustManagers>
+                    <sec:keyStore type="JKS" password="password" resource="keys/Truststore.jks"/>
+                </sec:trustManagers>
+                <sec:clientAuthentication want="true" required="false"/>
+            </httpj:tlsServerParameters>
+            <httpj:sessionSupport>true</httpj:sessionSupport>
+        </httpj:engine>
+    </httpj:engine-factory>
+    
+   <bean id="oauthProvider" class="org.apache.cxf.systest.jaxrs.security.oauth2.common.JCacheOAuthDataProviderImpl">
+       <constructor-arg><value>${testutil.ports.jaxrs-oauth2-grants2-jcache-public}</value></constructor-arg>
+       <constructor-arg><value>null</value></constructor-arg>
+       <constructor-arg><value>false</value></constructor-arg>
+       <constructor-arg><value>true</value></constructor-arg>
+   </bean>
+   
+   <bean id="authorizationService" class="org.apache.cxf.rs.security.oauth2.services.AuthorizationCodeGrantService">
+      <property name="dataProvider" ref="oauthProvider"/>
+      <property name="canSupportPublicClients" value="true"/>
+   </bean>
+   
+   <bean id="implicitService" class="org.apache.cxf.rs.security.oauth2.services.ImplicitGrantService">
+      <property name="dataProvider" ref="oauthProvider"/>
+   </bean>
+   
+   <bean id="refreshGrantHandler" class="org.apache.cxf.rs.security.oauth2.grants.refresh.RefreshTokenGrantHandler">
+      <property name="dataProvider" ref="oauthProvider"/>
+   </bean>
+   
+   <bean id="samlGrantHandler" class="org.apache.cxf.rs.security.oauth2.grants.saml.Saml2BearerGrantHandler">
+      <property name="dataProvider" ref="oauthProvider"/>
+   </bean>
+   
+   <bean id="jwtGrantHandler" class="org.apache.cxf.rs.security.oauth2.grants.jwt.JwtBearerGrantHandler">
+      <property name="dataProvider" ref="oauthProvider"/>
+   </bean>
+   
+   <bean id="tokenService" class="org.apache.cxf.rs.security.oauth2.services.AccessTokenService">
+      <property name="dataProvider" ref="oauthProvider"/>
+      <property name="canSupportPublicClients" value="true"/>
+      <property name="grantHandlers">
+         <list>
+             <ref bean="refreshGrantHandler"/>
+             <ref bean="samlGrantHandler"/>
+             <ref bean="jwtGrantHandler"/>
+         </list>
+      </property>
+   </bean>
+   
+   <bean id="callbackHandler" class="org.apache.cxf.systest.jaxrs.security.oauth2.common.CallbackHandlerImpl"/>
+   <bean id="basicAuthFilter" class="org.apache.cxf.systest.jaxrs.security.oauth2.common.WSS4JBasicAuthFilter">
+       <property name="callbackHandler" ref="callbackHandler"/>
+   </bean>
+   
+   <jaxrs:server 
+       depends-on="tls-config" 
+       address="https://localhost:${testutil.ports.jaxrs-oauth2-grants-jcache-public}/services">
+       <jaxrs:serviceBeans>
+           <ref bean="authorizationService"/>
+           <ref bean="implicitService"/>
+       </jaxrs:serviceBeans>
+       <jaxrs:providers>
+           <ref bean="basicAuthFilter"/>
+       </jaxrs:providers>
+       <jaxrs:properties>
+           <entry key="security.signature.properties" 
+                  value="org/apache/cxf/systest/jaxrs/security/bob.properties"/>
+           <entry key="rs.security.keystore.type" value="jks" />
+           <entry key="rs.security.keystore.alias" value="alice"/>
+           <entry key="rs.security.keystore.password" value="password"/>
+           <entry key="rs.security.keystore.file" value="keys/alice.jks" />
+           <entry key="rs.security.signature.algorithm" value="RS256" />
+       </jaxrs:properties>
+   </jaxrs:server>
+   
+   <jaxrs:server 
+       depends-on="tls-config" 
+       address="https://localhost:${testutil.ports.jaxrs-oauth2-grants-jcache-public}/services2">
+       <jaxrs:serviceBeans>
+           <ref bean="tokenService"/>
+       </jaxrs:serviceBeans>
+       <jaxrs:properties>
+           <entry key="security.signature.properties" 
+                  value="org/apache/cxf/systest/jaxrs/security/bob.properties"/>
+           <entry key="rs.security.keystore.type" value="jks" />
+           <entry key="rs.security.keystore.alias" value="alice"/>
+           <entry key="rs.security.keystore.password" value="password"/>
+           <entry key="rs.security.keystore.file" value="keys/alice.jks" />
+           <entry key="rs.security.signature.algorithm" value="RS256" />
+       </jaxrs:properties>
+   </jaxrs:server>
+   
+
+</beans>
diff --git a/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/grants/publicclient.xml b/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/grants/publicclient.xml
new file mode 100644
index 0000000..1663654
--- /dev/null
+++ b/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/grants/publicclient.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+ 
+ http://www.apache.org/licenses/LICENSE-2.0
+ 
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns:http="http://cxf.apache.org/transports/http/configuration"
+    xmlns:jaxws="http://cxf.apache.org/jaxws"
+    xmlns:cxf="http://cxf.apache.org/core"
+    xmlns:p="http://cxf.apache.org/policy"
+    xmlns:sec="http://cxf.apache.org/configuration/security"
+    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd http://cxf.apache.org/transports/http/configuration http://cxf.apache.org/schemas/configuration/http-conf.xsd http://cxf.apache.org/configuration/security http://cxf.apache.org/schemas/configuration/security.xsd http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd http://cxf.apache [...]
+    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/>
+    <cxf:bus>
+        <cxf:features>
+            <cxf:logging/>
+        </cxf:features>
+    </cxf:bus>
+    <http:conduit name="https://localhost.*">
+        <http:client ConnectionTimeout="3000000" ReceiveTimeout="3000000"/>
+        <http:tlsClientParameters disableCNCheck="true">
+            <sec:trustManagers>
+                <sec:keyStore type="JKS" password="password" resource="keys/Truststore.jks"/>
+            </sec:trustManagers>
+        </http:tlsClientParameters>
+    </http:conduit>
+</beans>
\ No newline at end of file