You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwebbeans.apache.org by rm...@apache.org on 2019/02/19 22:03:37 UTC
svn commit: r1853921 - in /openwebbeans/meecrowave/trunk/meecrowave-oauth2:
./ src/main/java/org/apache/meecrowave/oauth2/configuration/
src/main/resources/META-INF/ src/test/java/org/apache/meecrowave/oauth2/
Author: rmannibucau
Date: Tue Feb 19 22:03:37 2019
New Revision: 1853921
URL: http://svn.apache.org/viewvc?rev=1853921&view=rev
Log:
MEECROWAVE-183 ensure issuer can be propagated to the jwt token
Added:
openwebbeans/meecrowave/trunk/meecrowave-oauth2/src/main/resources/META-INF/beans.xml
Modified:
openwebbeans/meecrowave/trunk/meecrowave-oauth2/pom.xml
openwebbeans/meecrowave/trunk/meecrowave-oauth2/src/main/java/org/apache/meecrowave/oauth2/configuration/OAuth2Configurer.java
openwebbeans/meecrowave/trunk/meecrowave-oauth2/src/main/java/org/apache/meecrowave/oauth2/configuration/OAuth2Options.java
openwebbeans/meecrowave/trunk/meecrowave-oauth2/src/test/java/org/apache/meecrowave/oauth2/Keystores.java
openwebbeans/meecrowave/trunk/meecrowave-oauth2/src/test/java/org/apache/meecrowave/oauth2/OAuth2Test.java
Modified: openwebbeans/meecrowave/trunk/meecrowave-oauth2/pom.xml
URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-oauth2/pom.xml?rev=1853921&r1=1853920&r2=1853921&view=diff
==============================================================================
--- openwebbeans/meecrowave/trunk/meecrowave-oauth2/pom.xml (original)
+++ openwebbeans/meecrowave/trunk/meecrowave-oauth2/pom.xml Tue Feb 19 22:03:37 2019
@@ -106,6 +106,12 @@
<version>1.46</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.geronimo</groupId>
+ <artifactId>geronimo-jwt-auth</artifactId>
+ <version>1.0.1</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
Modified: openwebbeans/meecrowave/trunk/meecrowave-oauth2/src/main/java/org/apache/meecrowave/oauth2/configuration/OAuth2Configurer.java
URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-oauth2/src/main/java/org/apache/meecrowave/oauth2/configuration/OAuth2Configurer.java?rev=1853921&r1=1853920&r2=1853921&view=diff
==============================================================================
--- openwebbeans/meecrowave/trunk/meecrowave-oauth2/src/main/java/org/apache/meecrowave/oauth2/configuration/OAuth2Configurer.java (original)
+++ openwebbeans/meecrowave/trunk/meecrowave-oauth2/src/main/java/org/apache/meecrowave/oauth2/configuration/OAuth2Configurer.java Tue Feb 19 22:03:37 2019
@@ -18,6 +18,36 @@
*/
package org.apache.meecrowave.oauth2.configuration;
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptySet;
+import static java.util.Locale.ENGLISH;
+import static java.util.Optional.ofNullable;
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toMap;
+import static org.apache.cxf.rs.security.oauth2.common.AuthenticationMethod.PASSWORD;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.nio.charset.StandardCharsets;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import javax.annotation.PostConstruct;
+import javax.crypto.spec.SecretKeySpec;
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
import org.apache.catalina.realm.GenericPrincipal;
import org.apache.cxf.Bus;
import org.apache.cxf.common.util.StringUtils;
@@ -30,7 +60,11 @@ import org.apache.cxf.rs.security.jose.j
import org.apache.cxf.rs.security.jose.jwe.JweUtils;
import org.apache.cxf.rs.security.jose.jws.JwsSignatureProvider;
import org.apache.cxf.rs.security.jose.jws.JwsUtils;
+import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
+import org.apache.cxf.rs.security.oauth2.common.AccessToken;
import org.apache.cxf.rs.security.oauth2.common.OAuthRedirectionState;
+import org.apache.cxf.rs.security.oauth2.common.ServerAccessToken;
+import org.apache.cxf.rs.security.oauth2.common.TokenIntrospection;
import org.apache.cxf.rs.security.oauth2.common.UserSubject;
import org.apache.cxf.rs.security.oauth2.grants.AbstractGrantHandler;
import org.apache.cxf.rs.security.oauth2.grants.clientcred.ClientCredentialsGrantHandler;
@@ -59,32 +93,6 @@ import org.apache.meecrowave.Meecrowave;
import org.apache.meecrowave.oauth2.data.RefreshTokenEnabledProvider;
import org.apache.meecrowave.oauth2.provider.JCacheCodeDataProvider;
-import javax.annotation.PostConstruct;
-import javax.crypto.spec.SecretKeySpec;
-import javax.enterprise.context.ApplicationScoped;
-import javax.inject.Inject;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.core.MultivaluedMap;
-import java.io.IOException;
-import java.io.StringReader;
-import java.nio.charset.StandardCharsets;
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.function.Consumer;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptySet;
-import static java.util.Locale.ENGLISH;
-import static java.util.Optional.ofNullable;
-import static java.util.stream.Collectors.toMap;
-import static org.apache.cxf.rs.security.oauth2.common.AuthenticationMethod.PASSWORD;
-
@ApplicationScoped
public class OAuth2Configurer {
@Inject
@@ -99,28 +107,46 @@ public class OAuth2Configurer {
@Inject
private JCacheConfigurer jCacheConfigurer;
+ private OAuth2Options configuration;
+
private Consumer<AccessTokenService> tokenServiceConsumer;
private Consumer<RedirectionBasedGrantService> redirectionBasedGrantServiceConsumer;
private Consumer<AbstractTokenService> abstractTokenServiceConsumer;
- private OAuth2Options configuration;
private Map<String, String> securityProperties;
@PostConstruct // TODO: still some missing configuration for jwt etc to add/wire from OAuth2Options
private void preCompute() {
configuration = builder.getExtension(OAuth2Options.class);
+ final Function<JwtClaims, JwtClaims> customizeClaims = configuration.isUseJwtFormatForAccessTokens() ? claims -> {
+ if (claims.getIssuer() == null) {
+ claims.setIssuer(configuration.getJwtIssuer());
+ }
+ return claims;
+ } : identity();
+
AbstractOAuthDataProvider provider;
switch (configuration.getProvider().toLowerCase(ENGLISH)) {
case "jpa": {
if (!configuration.isAuthorizationCodeSupport()) { // else use code impl
- final JPAOAuthDataProvider jpaProvider = new JPAOAuthDataProvider();
+ final JPAOAuthDataProvider jpaProvider = new JPAOAuthDataProvider() {
+ @Override
+ protected JwtClaims createJwtAccessToken(final ServerAccessToken at) {
+ return customizeClaims.apply(super.createJwtAccessToken(at));
+ }
+ };
jpaProvider.setEntityManagerFactory(JPAAdapter.createEntityManagerFactory(configuration));
provider = jpaProvider;
break;
}
}
case "jpa-code": {
- final JPACodeDataProvider jpaProvider = new JPACodeDataProvider();
+ final JPACodeDataProvider jpaProvider = new JPACodeDataProvider() {
+ @Override
+ protected JwtClaims createJwtAccessToken(final ServerAccessToken at) {
+ return customizeClaims.apply(super.createJwtAccessToken(at));
+ }
+ };
jpaProvider.setEntityManagerFactory(JPAAdapter.createEntityManagerFactory(configuration));
provider = jpaProvider;
break;
@@ -129,7 +155,12 @@ public class OAuth2Configurer {
if (!configuration.isAuthorizationCodeSupport()) { // else use code impl
jCacheConfigurer.doSetup(configuration);
try {
- provider = new JCacheOAuthDataProvider(configuration.getJcacheConfigUri(), bus, configuration.isJcacheStoreJwtKeyOnly());
+ provider = new JCacheOAuthDataProvider(configuration.getJcacheConfigUri(), bus, configuration.isJcacheStoreJwtKeyOnly()) {
+ @Override
+ protected JwtClaims createJwtAccessToken(final ServerAccessToken at) {
+ return customizeClaims.apply(super.createJwtAccessToken(at));
+ }
+ };
} catch (final Exception e) {
throw new IllegalStateException(e);
}
@@ -138,7 +169,12 @@ public class OAuth2Configurer {
case "jcache-code":
jCacheConfigurer.doSetup(configuration);
try {
- provider = new JCacheCodeDataProvider(configuration, bus);
+ provider = new JCacheCodeDataProvider(configuration, bus) {
+ @Override
+ protected JwtClaims createJwtAccessToken(final ServerAccessToken at) {
+ return customizeClaims.apply(super.createJwtAccessToken(at));
+ }
+ };
} catch (final Exception e) {
throw new IllegalStateException(e);
}
@@ -146,12 +182,22 @@ public class OAuth2Configurer {
case "encrypted":
if (!configuration.isAuthorizationCodeSupport()) { // else use code impl
provider = new DefaultEncryptingOAuthDataProvider(
- new SecretKeySpec(configuration.getEncryptedKey().getBytes(StandardCharsets.UTF_8), configuration.getEncryptedAlgo()));
+ new SecretKeySpec(configuration.getEncryptedKey().getBytes(StandardCharsets.UTF_8), configuration.getEncryptedAlgo())) {
+ @Override
+ protected JwtClaims createJwtAccessToken(final ServerAccessToken at) {
+ return customizeClaims.apply(super.createJwtAccessToken(at));
+ }
+ };
break;
}
case "encrypted-code":
provider = new DefaultEncryptingCodeDataProvider(
- new SecretKeySpec(configuration.getEncryptedKey().getBytes(StandardCharsets.UTF_8), configuration.getEncryptedAlgo()));
+ new SecretKeySpec(configuration.getEncryptedKey().getBytes(StandardCharsets.UTF_8), configuration.getEncryptedAlgo())) {
+ @Override
+ protected JwtClaims createJwtAccessToken(final ServerAccessToken at) {
+ return customizeClaims.apply(super.createJwtAccessToken(at));
+ }
+ };
break;
default:
throw new IllegalArgumentException("Unsupported oauth2 provider: " + configuration.getProvider());
Modified: openwebbeans/meecrowave/trunk/meecrowave-oauth2/src/main/java/org/apache/meecrowave/oauth2/configuration/OAuth2Options.java
URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-oauth2/src/main/java/org/apache/meecrowave/oauth2/configuration/OAuth2Options.java?rev=1853921&r1=1853920&r2=1853921&view=diff
==============================================================================
--- openwebbeans/meecrowave/trunk/meecrowave-oauth2/src/main/java/org/apache/meecrowave/oauth2/configuration/OAuth2Options.java (original)
+++ openwebbeans/meecrowave/trunk/meecrowave-oauth2/src/main/java/org/apache/meecrowave/oauth2/configuration/OAuth2Options.java Tue Feb 19 22:03:37 2019
@@ -76,6 +76,9 @@ public class OAuth2Options implements Cl
@CliOption(name = "oauth2-jwt-access-token-claim-map", description = "The jwt claims configuration")
private String jwtAccessTokenClaimMap;
+ @CliOption(name = "oauth2-jwt-issuer", description = "The jwt issuer (ignored if not set)")
+ private String jwtIssuer;
+
@CliOption(name = "oauth2-provider", description = "Which provider type to use: jcache[-code], jpa[-code], encrypted[-code]")
private String provider = "jcache";
@@ -536,4 +539,12 @@ public class OAuth2Options implements Cl
public void setJcacheJmx(final boolean jcacheJmx) {
this.jcacheJmx = jcacheJmx;
}
+
+ public String getJwtIssuer() {
+ return jwtIssuer;
+ }
+
+ public void setJwtIssuer(final String jwtIssuer) {
+ this.jwtIssuer = jwtIssuer;
+ }
}
Added: openwebbeans/meecrowave/trunk/meecrowave-oauth2/src/main/resources/META-INF/beans.xml
URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-oauth2/src/main/resources/META-INF/beans.xml?rev=1853921&view=auto
==============================================================================
--- openwebbeans/meecrowave/trunk/meecrowave-oauth2/src/main/resources/META-INF/beans.xml (added)
+++ openwebbeans/meecrowave/trunk/meecrowave-oauth2/src/main/resources/META-INF/beans.xml Tue Feb 19 22:03:37 2019
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<!--
+ 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 bean-discovery-mode="all" version="2.0"
+ xmlns="http://xmlns.jcp.org/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="
+ http://xmlns.jcp.org/xml/ns/javaee
+ http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd">
+
+ <trim/>
+</beans>
Modified: openwebbeans/meecrowave/trunk/meecrowave-oauth2/src/test/java/org/apache/meecrowave/oauth2/Keystores.java
URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-oauth2/src/test/java/org/apache/meecrowave/oauth2/Keystores.java?rev=1853921&r1=1853920&r2=1853921&view=diff
==============================================================================
--- openwebbeans/meecrowave/trunk/meecrowave-oauth2/src/test/java/org/apache/meecrowave/oauth2/Keystores.java (original)
+++ openwebbeans/meecrowave/trunk/meecrowave-oauth2/src/test/java/org/apache/meecrowave/oauth2/Keystores.java Tue Feb 19 22:03:37 2019
@@ -32,6 +32,7 @@ import java.io.OutputStream;
import java.security.KeyStore;
import java.security.Principal;
import java.security.PrivateKey;
+import java.security.PublicKey;
import java.security.cert.X509Certificate;
public final class Keystores {
@@ -39,29 +40,30 @@ public final class Keystores {
// no-op
}
- public static void create(final File keystore) throws Exception {
+ public static PublicKey create(final File keystore) throws Exception {
CryptoUtils.installBouncyCastleProvider();
final KeyStore ks = KeyStore.getInstance("JKS");
ks.load(null, "password".toCharArray());
- final CertAndKeyGen keyGen = new CertAndKeyGen("RSA", "SHA1WithRSA", null);
+ final CertAndKeyGen keyGen = new CertAndKeyGen("RSA", "SHA256WithRSA", null);
keyGen.generate(2048);
- PrivateKey rootPrivateKey = keyGen.getPrivateKey();
+ final PrivateKey rootPrivateKey = keyGen.getPrivateKey();
X509Certificate rootCertificate = keyGen.getSelfCertificate(new X500Name("cn=root"), (long) 365 * 24 * 60 * 60);
- final CertAndKeyGen keyGen1 = new CertAndKeyGen("RSA", "SHA1WithRSA", null);
+ final CertAndKeyGen keyGen1 = new CertAndKeyGen("RSA", "SHA256WithRSA", null);
keyGen1.generate(2048);
final PrivateKey middlePrivateKey = keyGen1.getPrivateKey();
X509Certificate middleCertificate = keyGen1.getSelfCertificate(new X500Name("CN=MIDDLE"), (long) 365 * 24 * 60 * 60);
//Generate leaf certificate
- final CertAndKeyGen keyGen2 = new CertAndKeyGen("RSA", "SHA1WithRSA", null);
+ final CertAndKeyGen keyGen2 = new CertAndKeyGen("RSA", "SHA256WithRSA", null);
keyGen2.generate(2048);
final PrivateKey topPrivateKey = keyGen2.getPrivateKey();
+
X509Certificate topCertificate = keyGen2.getSelfCertificate(new X500Name("cn=root"), (long) 365 * 24 * 60 * 60);
rootCertificate = createSignedCertificate(rootCertificate, rootCertificate, rootPrivateKey);
@@ -77,6 +79,8 @@ public final class Keystores {
try (final OutputStream os = new FileOutputStream(keystore)) {
ks.store(os, "password".toCharArray());
}
+
+ return keyGen2.getPublicKey();
}
private static X509Certificate createSignedCertificate(final X509Certificate cetrificate, final X509Certificate issuerCertificate,
Modified: openwebbeans/meecrowave/trunk/meecrowave-oauth2/src/test/java/org/apache/meecrowave/oauth2/OAuth2Test.java
URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-oauth2/src/test/java/org/apache/meecrowave/oauth2/OAuth2Test.java?rev=1853921&r1=1853920&r2=1853921&view=diff
==============================================================================
--- openwebbeans/meecrowave/trunk/meecrowave-oauth2/src/test/java/org/apache/meecrowave/oauth2/OAuth2Test.java (original)
+++ openwebbeans/meecrowave/trunk/meecrowave-oauth2/src/test/java/org/apache/meecrowave/oauth2/OAuth2Test.java Tue Feb 19 22:03:37 2019
@@ -30,10 +30,14 @@ import static org.junit.Assert.assertTru
import static org.junit.Assert.fail;
import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
+import java.security.PublicKey;
import java.util.Base64;
import java.util.function.BiFunction;
+import java.util.stream.Stream;
import javax.cache.Cache;
import javax.cache.CacheManager;
@@ -56,15 +60,22 @@ import org.apache.cxf.rs.security.oauth2
import org.apache.cxf.rs.security.oauth2.common.OAuthAuthorizationData;
import org.apache.cxf.rs.security.oauth2.provider.OAuthJSONProvider;
import org.apache.cxf.rs.security.oauth2.utils.OAuthConstants;
+import org.apache.geronimo.microprofile.impl.jwtauth.config.GeronimoJwtAuthConfig;
+import org.apache.geronimo.microprofile.impl.jwtauth.jwt.DateValidator;
+import org.apache.geronimo.microprofile.impl.jwtauth.jwt.JwtParser;
+import org.apache.geronimo.microprofile.impl.jwtauth.jwt.KidMapper;
+import org.apache.geronimo.microprofile.impl.jwtauth.jwt.SignatureValidator;
import org.apache.meecrowave.Meecrowave;
import org.apache.meecrowave.junit.MeecrowaveRule;
import org.apache.meecrowave.oauth2.provider.JCacheCodeDataProvider;
+import org.eclipse.microprofile.jwt.JsonWebToken;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
public class OAuth2Test {
private static final File KEYSTORE = new File("target/OAuth2Test/keystore.jceks");
+ private static PublicKey PUBLIC_KEY;
@ClassRule
public static final MeecrowaveRule MEECROWAVE = new MeecrowaveRule(
@@ -72,6 +83,7 @@ public class OAuth2Test {
.user("test", "pwd").role("test", "admin")
// jwt requires more config
.property("oauth2-use-jwt-format-for-access-token", "true")
+ .property("oauth2-jwt-issuer", "myissuer")
// auth code support is optional so activate it
.property("oauth2-authorization-code-support", "true")
// auth code jose setup to store the tokens
@@ -90,7 +102,7 @@ public class OAuth2Test {
@BeforeClass
public static void createKeyStore() throws Exception {
- Keystores.create(KEYSTORE);
+ PUBLIC_KEY = Keystores.create(KEYSTORE);
}
@Test
@@ -112,6 +124,7 @@ public class OAuth2Test {
assertEquals(3600, token.getExpiresIn());
assertNotEquals(0, token.getIssuedAt());
assertNotNull(token.getRefreshToken());
+ validateJwt(token);
} finally {
client.close();
}
@@ -245,4 +258,59 @@ public class OAuth2Test {
fail(e.getMessage());
}
}
+
+ private void validateJwt(final ClientAccessToken token) {
+ final JwtParser parser = new JwtParser();
+ final KidMapper kidMapper = new KidMapper();
+ final DateValidator dateValidator = new DateValidator();
+ final SignatureValidator signatureValidator = new SignatureValidator();
+ final GeronimoJwtAuthConfig config = (value, def) -> {
+ switch (value) {
+ case "issuer.default":
+ return "myissuer";
+ case "jwt.header.kid.default":
+ return "defaultkid";
+ case "public-key.default":
+ return Base64.getEncoder().encodeToString(PUBLIC_KEY.getEncoded());
+ default:
+ return def;
+ }
+ };
+ setField(kidMapper, "config", config);
+ setField(dateValidator, "config", config);
+ setField(parser, "config", config);
+ setField(signatureValidator, "config", config);
+ setField(parser, "kidMapper", kidMapper);
+ setField(parser, "dateValidator", dateValidator);
+ setField(parser, "signatureValidator", signatureValidator);
+ Stream.of(dateValidator, signatureValidator, kidMapper, parser).forEach(this::init);
+ final JsonWebToken jsonWebToken = parser.parse(token.getTokenKey());
+ assertNotNull(jsonWebToken);
+ assertEquals("myissuer", jsonWebToken.getIssuer());
+ assertEquals("test", JsonString.class.cast(jsonWebToken.getClaim("username")).getString());
+ }
+
+ private void init(final Object o) {
+ try {
+ final Method init = o.getClass().getDeclaredMethod("init");
+ if (!init.isAccessible()) {
+ init.setAccessible(true);
+ }
+ init.invoke(o);
+ } catch (final Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private void setField(final Object instance, final String field, final Object value) {
+ try {
+ final Field declaredField = instance.getClass().getDeclaredField(field);
+ if (!declaredField.isAccessible()) {
+ declaredField.setAccessible(true);
+ }
+ declaredField.set(instance, value);
+ } catch (final Exception e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
}