TOMEE-2247 - Refactor to support multiple keys.
Project: http://git-wip-us.apache.org/repos/asf/tomee/repo Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/be942d50 Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/be942d50 Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/be942d50 Branch: refs/heads/master Commit: be942d5012a432d2be92e27cc272469bddbb679c Parents: 46934a0 Author: Roberto Cortez <[email protected]> Authored: Tue Sep 25 19:00:47 2018 +0100 Committer: Roberto Cortez <[email protected]> Committed: Fri Dec 7 18:11:18 2018 +0000 ---------------------------------------------------------------------- .../tomee/microprofile/jwt/MPJWTFilter.java | 1 - .../config/ConfigurableJWTAuthContextInfo.java | 55 ++++++++++---------- .../jwt/config/JWTAuthContextInfo.java | 43 +++++++-------- .../DefaultJWTCallerPrincipalFactory.java | 4 +- .../tck/jwt/JWTAuthContextInfoProvider.java | 9 +--- .../microprofile/tck/jwt/TCKTokenParser.java | 40 -------------- ...lipse.microprofile.jwt.tck.util.ITokenParser | 1 - 7 files changed, 51 insertions(+), 102 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tomee/blob/be942d50/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java ---------------------------------------------------------------------- diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java index 9633819..ee3be1b 100644 --- a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java +++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java @@ -16,7 +16,6 @@ */ package org.apache.tomee.microprofile.jwt; -import org.apache.openejb.loader.SystemInstance; import org.apache.tomee.microprofile.jwt.config.ConfigurableJWTAuthContextInfo; import org.apache.tomee.microprofile.jwt.config.JWTAuthContextInfo; import org.apache.tomee.microprofile.jwt.principal.JWTCallerPrincipalFactory; http://git-wip-us.apache.org/repos/asf/tomee/blob/be942d50/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/ConfigurableJWTAuthContextInfo.java ---------------------------------------------------------------------- diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/ConfigurableJWTAuthContextInfo.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/ConfigurableJWTAuthContextInfo.java index 3705448..d5e302e 100644 --- a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/ConfigurableJWTAuthContextInfo.java +++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/ConfigurableJWTAuthContextInfo.java @@ -41,6 +41,7 @@ import java.io.StringReader; import java.io.StringWriter; import java.net.URISyntaxException; import java.net.URL; +import java.security.Key; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.interfaces.RSAPublicKey; @@ -48,6 +49,7 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; import java.util.Base64; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.function.Supplier; @@ -91,51 +93,50 @@ public class ConfigurableJWTAuthContextInfo { } private JWTAuthContextInfo createJWTAuthContextInfo() { - final Stream<Supplier<Optional<RSAPublicKey>>> possiblePublicKeys = - Stream.of(() -> getVerifierPublicKey().map(this::readPublicKey), - () -> getPublicKeyLocation().map(this::readPublicKeyFromLocation)); + final Stream<Supplier<Optional<List<Key>>>> possiblePublicKeys = + Stream.of(() -> getVerifierPublicKey().map(this::readPublicKeys), + () -> getPublicKeyLocation().map(this::readPublicKeysFromLocation)); return possiblePublicKeys .map(Supplier::get) .filter(Optional::isPresent) .map(Optional::get) .findFirst() - .map(key -> new JWTAuthContextInfo(key, getIssuer().orElse(null))) + .map(keys -> JWTAuthContextInfo.authContextInfo(keys, getIssuer().orElse(null))) .orElse(null); } - private RSAPublicKey readPublicKey(final String publicKey) { - final Stream<Supplier<Optional<RSAPublicKey>>> possiblePublicKeysParses = + private List<Key> readPublicKeys(final String publicKey) { + final Stream<Supplier<List<Key>>> possiblePublicKeysParses = Stream.of(() -> parsePCKS8(publicKey), () -> parseJwk(publicKey), () -> parseJwk(new String(Base64.getDecoder().decode(publicKey)))); return possiblePublicKeysParses .map(Supplier::get) - .filter(Optional::isPresent) - .map(Optional::get) + .filter(keys -> !keys.isEmpty()) .findFirst() .orElseThrow(() -> new DeploymentException("Could not read MicroProfile Public Key: " + publicKey)); } - private RSAPublicKey readPublicKeyFromLocation(final String publicKeyLocation) { + private List<Key> readPublicKeysFromLocation(final String publicKeyLocation) { final Stream<Supplier<Optional<String>>> possiblePublicKeysLocations = - Stream.of(() -> readPublicKeyFromClasspath(publicKeyLocation), - () -> readPublicKeyFromFile(publicKeyLocation), - () -> readPublicKeyFromHttp(publicKeyLocation), - () -> readPublicKeyFromUrl(publicKeyLocation)); + Stream.of(() -> readPublicKeysFromClasspath(publicKeyLocation), + () -> readPublicKeysFromFile(publicKeyLocation), + () -> readPublicKeysFromHttp(publicKeyLocation), + () -> readPublicKeysFromUrl(publicKeyLocation)); return possiblePublicKeysLocations .map(Supplier::get) .filter(Optional::isPresent) .map(Optional::get) .findFirst() - .map(this::readPublicKey) + .map(this::readPublicKeys) .orElseThrow(() -> new DeploymentException("Could not read MicroProfile Public Key from Location: " + publicKeyLocation)); } - private Optional<String> readPublicKeyFromClasspath(final String publicKeyLocation) { + private Optional<String> readPublicKeysFromClasspath(final String publicKeyLocation) { try { final InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(publicKeyLocation); @@ -149,7 +150,7 @@ public class ConfigurableJWTAuthContextInfo { } } - private Optional<String> readPublicKeyFromFile(final String publicKeyLocation) { + private Optional<String> readPublicKeysFromFile(final String publicKeyLocation) { if (!publicKeyLocation.startsWith("file")) { return Optional.empty(); } @@ -171,7 +172,7 @@ public class ConfigurableJWTAuthContextInfo { } } - private Optional<String> readPublicKeyFromHttp(final String publicKeyLocation) { + private Optional<String> readPublicKeysFromHttp(final String publicKeyLocation) { if (!publicKeyLocation.startsWith("http")) { return Optional.empty(); } @@ -185,7 +186,7 @@ public class ConfigurableJWTAuthContextInfo { } } - private Optional<String> readPublicKeyFromUrl(final String publicKeyLocation) { + private Optional<String> readPublicKeysFromUrl(final String publicKeyLocation) { try { final URL locationURL = new URL(publicKeyLocation); return Optional.of(readPublicKeyFromInputStream(locationURL.openStream())); @@ -208,39 +209,39 @@ public class ConfigurableJWTAuthContextInfo { return content.toString(); } - private Optional<RSAPublicKey> parsePCKS8(final String publicKey) { + private List<Key> parsePCKS8(final String publicKey) { try { final X509EncodedKeySpec spec = new X509EncodedKeySpec(normalizeAndDecodePCKS8(publicKey)); final KeyFactory kf = KeyFactory.getInstance("RSA"); - return Optional.of((RSAPublicKey) kf.generatePublic(spec)); + return Collections.singletonList(kf.generatePublic(spec)); } catch (final NoSuchAlgorithmException | InvalidKeySpecException | IllegalArgumentException e) { - return Optional.empty(); + return Collections.emptyList(); } } - private Optional<RSAPublicKey> parseJwk(final String publicKey) { + private List<Key> parseJwk(final String publicKey) { final JsonObject jwk; try { jwk = Json.createReader(new StringReader(publicKey)).readObject(); } catch (final JsonParsingException e) { - return Optional.empty(); + return Collections.emptyList(); } validateJwk(jwk); try { - return Optional.of((RSAPublicKey) JsonWebKey.Factory.newJwk(publicKey).getKey()); + return Collections.singletonList(JsonWebKey.Factory.newJwk(publicKey).getKey()); } catch (final JoseException e) { throw new DeploymentException("Could not read MicroProfile Public Key JWK.", e); } } - private Optional<List<RSAPublicKey>> parseJwks(final String publicKey) { + private List<Key> parseJwks(final String publicKey) { final JsonObject jwks; try { jwks = Json.createReader(new StringReader(publicKey)).readObject(); } catch (final JsonParsingException e) { - return Optional.empty(); + return Collections.emptyList(); } try { @@ -260,7 +261,7 @@ public class ConfigurableJWTAuthContextInfo { .map(JsonWebKey::getKey) .map(key -> (RSAPublicKey) key) .collect(Collectors.toList()); - return Optional.of(keys); + return Collections.unmodifiableList(keys); } catch (final JoseException e) { throw new DeploymentException("Could not read MicroProfile Public Key JWK.", e); } http://git-wip-us.apache.org/repos/asf/tomee/blob/be942d50/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfo.java ---------------------------------------------------------------------- diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfo.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfo.java index a969515..02132c0 100644 --- a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfo.java +++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfo.java @@ -16,52 +16,49 @@ */ package org.apache.tomee.microprofile.jwt.config; -import java.security.interfaces.RSAPublicKey; +import java.security.Key; +import java.util.Collections; +import java.util.List; /** * The public key and expected issuer needed to validate a token. */ public class JWTAuthContextInfo { - - private RSAPublicKey signerKey; + private List<Key> signerKeys; private String issuedBy; private int expGracePeriodSecs = 60; - public JWTAuthContextInfo() { + private JWTAuthContextInfo(final Key signerKey, final String issuedBy) { + this.signerKeys = Collections.singletonList(signerKey); + this.issuedBy = issuedBy; } - public JWTAuthContextInfo(final RSAPublicKey signerKey, final String issuedBy) { - this.signerKey = signerKey; + private JWTAuthContextInfo(final List<Key> signerKeys, final String issuedBy) { + this.signerKeys = Collections.unmodifiableList(signerKeys); this.issuedBy = issuedBy; } - public JWTAuthContextInfo(final JWTAuthContextInfo orig) { - this.signerKey = orig.signerKey; - this.issuedBy = orig.issuedBy; - this.expGracePeriodSecs = orig.expGracePeriodSecs; + public static JWTAuthContextInfo authContextInfo(final Key signerKey, final String issuedBy) { + return new JWTAuthContextInfo(signerKey, issuedBy); } - public RSAPublicKey getSignerKey() { - return signerKey; + public static JWTAuthContextInfo authContextInfo(final List<Key> signerKeys, final String issuedBy) { + return new JWTAuthContextInfo(signerKeys, issuedBy); } - public void setSignerKey(final RSAPublicKey signerKey) { - this.signerKey = signerKey; + public List<Key> getSignerKeys() { + return signerKeys; } - public String getIssuedBy() { - return issuedBy; + public Key getSignerKey(final String kid) { + return signerKeys.get(0); } - public void setIssuedBy(final String issuedBy) { - this.issuedBy = issuedBy; + public String getIssuedBy() { + return issuedBy; } public int getExpGracePeriodSecs() { return expGracePeriodSecs; } - - public void setExpGracePeriodSecs(final int expGracePeriodSecs) { - this.expGracePeriodSecs = expGracePeriodSecs; - } -} \ No newline at end of file +} http://git-wip-us.apache.org/repos/asf/tomee/blob/be942d50/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipalFactory.java ---------------------------------------------------------------------- diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipalFactory.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipalFactory.java index feb2008..f19b108 100644 --- a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipalFactory.java +++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipalFactory.java @@ -50,7 +50,7 @@ public class DefaultJWTCallerPrincipalFactory extends JWTCallerPrincipalFactory .setRequireSubject() .setSkipDefaultAudienceValidation() .setExpectedIssuer(authContextInfo.getIssuedBy()) - .setVerificationKey(authContextInfo.getSignerKey()) + .setVerificationKey(authContextInfo.getSignerKey("")) .setJwsAlgorithmConstraints( new AlgorithmConstraints(AlgorithmConstraints.ConstraintType.WHITELIST, AlgorithmIdentifiers.RSA_USING_SHA256)); @@ -89,4 +89,4 @@ public class DefaultJWTCallerPrincipalFactory extends JWTCallerPrincipalFactory return principal; } -} \ No newline at end of file +} http://git-wip-us.apache.org/repos/asf/tomee/blob/be942d50/tck/microprofile-tck/jwt/src/test/java/org/apache/tomee/microprofile/tck/jwt/JWTAuthContextInfoProvider.java ---------------------------------------------------------------------- diff --git a/tck/microprofile-tck/jwt/src/test/java/org/apache/tomee/microprofile/tck/jwt/JWTAuthContextInfoProvider.java b/tck/microprofile-tck/jwt/src/test/java/org/apache/tomee/microprofile/tck/jwt/JWTAuthContextInfoProvider.java index 6dc1a8f..ee38e49 100644 --- a/tck/microprofile-tck/jwt/src/test/java/org/apache/tomee/microprofile/tck/jwt/JWTAuthContextInfoProvider.java +++ b/tck/microprofile-tck/jwt/src/test/java/org/apache/tomee/microprofile/tck/jwt/JWTAuthContextInfoProvider.java @@ -33,11 +33,6 @@ public class JWTAuthContextInfoProvider { @Produces Optional<JWTAuthContextInfo> getOptionalContextInfo() throws NoSuchAlgorithmException, InvalidKeySpecException { - JWTAuthContextInfo contextInfo = new JWTAuthContextInfo(); - - // todo use MP Config to load the configuration - contextInfo.setIssuedBy("https://server.example.com"); - final String pemEncoded = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y2jy0CfEq" + "Fyy46R0o7S8TKpsx5xbHKoU1VWg6QkQm+ntyIv1p4kE1sPEQO73+HY8+Bzs75XwR" + "TYL1BmR1w8J5hmjVWjc6R2BTBGAYRPFRhor3kpM6ni2SPmNNhurEAHw7TaqszP5e" + @@ -51,9 +46,7 @@ public class JWTAuthContextInfoProvider { final KeyFactory kf = KeyFactory.getInstance("RSA"); final RSAPublicKey pk = (RSAPublicKey) kf.generatePublic(spec); - contextInfo.setSignerKey(pk); - - return Optional.of(contextInfo); + return Optional.of(JWTAuthContextInfo.authContextInfo(pk, "https://server.example.com")); } @Produces http://git-wip-us.apache.org/repos/asf/tomee/blob/be942d50/tck/microprofile-tck/jwt/src/test/java/org/apache/tomee/microprofile/tck/jwt/TCKTokenParser.java ---------------------------------------------------------------------- diff --git a/tck/microprofile-tck/jwt/src/test/java/org/apache/tomee/microprofile/tck/jwt/TCKTokenParser.java b/tck/microprofile-tck/jwt/src/test/java/org/apache/tomee/microprofile/tck/jwt/TCKTokenParser.java deleted file mode 100644 index e13709d..0000000 --- a/tck/microprofile-tck/jwt/src/test/java/org/apache/tomee/microprofile/tck/jwt/TCKTokenParser.java +++ /dev/null @@ -1,40 +0,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. - */ -package org.apache.tomee.microprofile.tck.jwt; - -import org.apache.tomee.microprofile.jwt.config.JWTAuthContextInfo; -import org.apache.tomee.microprofile.jwt.principal.DefaultJWTCallerPrincipalFactory; -import org.apache.tomee.microprofile.jwt.principal.JWTCallerPrincipalFactory; -import org.eclipse.microprofile.jwt.JsonWebToken; -import org.eclipse.microprofile.jwt.tck.util.ITokenParser; - -import java.security.PublicKey; -import java.security.interfaces.RSAPublicKey; - -/** - * MP-JWT TCK harness class to parse a token string - */ -public class TCKTokenParser implements ITokenParser { - - @Override - public JsonWebToken parse(final String bearerToken, final String issuer, final PublicKey publicKey) throws Exception { - final JWTAuthContextInfo authContextInfo = new JWTAuthContextInfo((RSAPublicKey) publicKey, issuer); - final JWTCallerPrincipalFactory factory = DefaultJWTCallerPrincipalFactory.instance(); - return factory.parse(bearerToken, authContextInfo); - } - -} http://git-wip-us.apache.org/repos/asf/tomee/blob/be942d50/tck/microprofile-tck/jwt/src/test/resources/META-INF/services/org.eclipse.microprofile.jwt.tck.util.ITokenParser ---------------------------------------------------------------------- diff --git a/tck/microprofile-tck/jwt/src/test/resources/META-INF/services/org.eclipse.microprofile.jwt.tck.util.ITokenParser b/tck/microprofile-tck/jwt/src/test/resources/META-INF/services/org.eclipse.microprofile.jwt.tck.util.ITokenParser deleted file mode 100644 index f8d8098..0000000 --- a/tck/microprofile-tck/jwt/src/test/resources/META-INF/services/org.eclipse.microprofile.jwt.tck.util.ITokenParser +++ /dev/null @@ -1 +0,0 @@ -org.apache.tomee.microprofile.tck.jwt.TCKTokenParser
