http://git-wip-us.apache.org/repos/asf/knox/blob/58780d37/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/impl/JWTToken.java ---------------------------------------------------------------------- diff --cc gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/impl/JWTToken.java index 27b1a30,0000000..e765c27 mode 100644,000000..100644 --- a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/impl/JWTToken.java +++ b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/impl/JWTToken.java @@@ -1,273 -1,0 +1,281 @@@ + /** + * 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.knox.gateway.services.security.token.impl; + - import java.io.UnsupportedEncodingException; +import java.text.ParseException; +import java.util.Date; +import java.util.ArrayList; +import java.util.List; - import java.util.Map; + - import org.apache.commons.codec.binary.Base64; +import org.apache.knox.gateway.i18n.messages.MessagesFactory; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.JWSSigner; +import com.nimbusds.jose.JWSVerifier; +import com.nimbusds.jose.Payload; +import com.nimbusds.jose.util.Base64URL; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; + +public class JWTToken implements JWT { + private static JWTProviderMessages log = MessagesFactory.get( JWTProviderMessages.class ); + + SignedJWT jwt = null; + + private JWTToken(String header, String claims, String signature) throws ParseException { + jwt = new SignedJWT(new Base64URL(header), new Base64URL(claims), new Base64URL(signature)); + } + + public JWTToken(String serializedJWT) throws ParseException { + try { + jwt = SignedJWT.parse(serializedJWT); + } catch (ParseException e) { + log.unableToParseToken(e); + throw e; + } + } + + public JWTToken(String alg, String[] claimsArray) { + this(alg, claimsArray, null); + } + + public JWTToken(String alg, String[] claimsArray, List<String> audiences) { + JWSHeader header = new JWSHeader(new JWSAlgorithm(alg)); + + if (claimsArray[2] != null) { + if (audiences == null) { + audiences = new ArrayList<String>(); + } + audiences.add(claimsArray[2]); + } + JWTClaimsSet claims = null; + JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder() + .issuer(claimsArray[0]) + .subject(claimsArray[1]) + .audience(audiences); + if(claimsArray[3] != null) { + builder = builder.expirationTime(new Date(Long.parseLong(claimsArray[3]))); + } + + claims = builder.build(); + + jwt = new SignedJWT(header, claims); + } + + /* (non-Javadoc) - * @see JWT#getPayloadToSign() ++ * @see org.apache.knox.gateway.services.security.token.impl.JWT#getPayloadToSign() + */ + @Override + public String getHeader() { + JWSHeader header = jwt.getHeader(); + return header.toString(); + } + + /* (non-Javadoc) - * @see JWT#getPayloadToSign() ++ * @see org.apache.knox.gateway.services.security.token.impl.JWT#getPayloadToSign() + */ + @Override + public String getClaims() { + String c = null; + JWTClaimsSet claims = null; + try { + claims = (JWTClaimsSet) jwt.getJWTClaimsSet(); + c = claims.toJSONObject().toJSONString(); + } catch (ParseException e) { + log.unableToParseToken(e); + } + return c; + } + + /* (non-Javadoc) - * @see JWT#getPayloadToSign() ++ * @see org.apache.knox.gateway.services.security.token.impl.JWT#getPayloadToSign() + */ + @Override + public String getPayload() { + Payload payload = jwt.getPayload(); + return payload.toString(); + } + + public String toString() { + return jwt.serialize(); + } + + /* (non-Javadoc) - * @see JWT#setSignaturePayload(byte[]) ++ * @see org.apache.knox.gateway.services.security.token.impl.JWT#setSignaturePayload(byte[]) + */ + @Override + public void setSignaturePayload(byte[] payload) { +// this.payload = payload; + } + + /* (non-Javadoc) - * @see JWT#getSignaturePayload() ++ * @see org.apache.knox.gateway.services.security.token.impl.JWT#getSignaturePayload() + */ + @Override + public byte[] getSignaturePayload() { + byte[] b = null; + Base64URL b64 = jwt.getSignature(); + if (b64 != null) { + b = b64.decode(); + } + return b; + } + + public static JWTToken parseToken(String wireToken) throws ParseException { + log.parsingToken(wireToken); + String[] parts = wireToken.split("\\."); + return new JWTToken(parts[0], parts[1], parts[2]); + } + + /* (non-Javadoc) - * @see JWT#getClaim(java.lang.String) ++ * @see org.apache.knox.gateway.services.security.token.impl.JWT#getClaim(java.lang.String) + */ + @Override + public String getClaim(String claimName) { + String claim = null; + + try { + claim = jwt.getJWTClaimsSet().getStringClaim(claimName); + } catch (ParseException e) { + log.unableToParseToken(e); + } + + return claim; + } + + /* (non-Javadoc) - * @see JWT#getSubject() ++ * @see org.apache.knox.gateway.services.security.token.impl.JWT#getSubject() + */ + @Override + public String getSubject() { + return getClaim(JWT.SUBJECT); + } + + /* (non-Javadoc) - * @see JWT#getIssuer() ++ * @see org.apache.knox.gateway.services.security.token.impl.JWT#getIssuer() + */ + @Override + public String getIssuer() { + return getClaim(JWT.ISSUER); + } + + /* (non-Javadoc) - * @see JWT#getAudience() ++ * @see org.apache.knox.gateway.services.security.token.impl.JWT#getAudience() + */ + @Override + public String getAudience() { + String[] claim = null; + String c = null; + + claim = getAudienceClaims(); + if (claim != null) { + c = claim[0]; + } + + return c; + } + + /* (non-Javadoc) - * @see JWT#getAudienceClaims() ++ * @see org.apache.knox.gateway.services.security.token.impl.JWT#getAudienceClaims() + */ + @Override + public String[] getAudienceClaims() { + String[] claims = null; + + try { + claims = jwt.getJWTClaimsSet().getStringArrayClaim(JWT.AUDIENCE); + } catch (ParseException e) { + log.unableToParseToken(e); + } + + return claims; + } + + /* (non-Javadoc) - * @see JWT#getExpires() ++ * @see org.apache.knox.gateway.services.security.token.impl.JWT#getExpires() + */ + @Override + public String getExpires() { + Date expires = getExpiresDate(); + if (expires != null) { + return String.valueOf(expires.getTime()); + } + return null; + } + + @Override + public Date getExpiresDate() { + Date date = null; + try { + date = jwt.getJWTClaimsSet().getExpirationTime(); + } catch (ParseException e) { + log.unableToParseToken(e); + } + return date; + } + ++ @Override ++ public Date getNotBeforeDate() { ++ Date date = null; ++ try { ++ date = jwt.getJWTClaimsSet().getNotBeforeTime(); ++ } catch (ParseException e) { ++ log.unableToParseToken(e); ++ } ++ return date; ++ } ++ + /* (non-Javadoc) - * @see JWT#getPrincipal() ++ * @see org.apache.knox.gateway.services.security.token.impl.JWT#getPrincipal() + */ + @Override + public String getPrincipal() { + return getClaim(JWT.PRINCIPAL); + } + + + /* (non-Javadoc) + * @see org.apache.knox.gateway.services.security.token.impl.JWT#sign(JWSSigner) + */ + @Override + public void sign(JWSSigner signer) { + try { + jwt.sign(signer); + } catch (JOSEException e) { + log.unableToSignToken(e); + } + } + + /* (non-Javadoc) + * @see org.apache.knox.gateway.services.security.token.impl.JWT#verify(JWSVerifier) + */ + public boolean verify(JWSVerifier verifier) { + boolean rc = false; + + try { + rc = jwt.verify(verifier); + } catch (JOSEException e) { + // TODO Auto-generated catch block + log.unableToVerifyToken(e); + } + + return rc; + } +}
http://git-wip-us.apache.org/repos/asf/knox/blob/58780d37/gateway-spi/src/test/java/org/apache/knox/gateway/services/security/token/impl/JWTTokenTest.java ---------------------------------------------------------------------- diff --cc gateway-spi/src/test/java/org/apache/knox/gateway/services/security/token/impl/JWTTokenTest.java index 1b0df9e,0000000..2c23e92 mode 100644,000000..100644 --- a/gateway-spi/src/test/java/org/apache/knox/gateway/services/security/token/impl/JWTTokenTest.java +++ b/gateway-spi/src/test/java/org/apache/knox/gateway/services/security/token/impl/JWTTokenTest.java @@@ -1,223 -1,0 +1,240 @@@ +/** + * 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.knox.gateway.services.security.token.impl; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; ++import java.text.ParseException; +import java.util.ArrayList; +import java.util.Date; ++import java.util.List; + ++import org.junit.BeforeClass; +import org.junit.Test; + +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSSigner; +import com.nimbusds.jose.JWSVerifier; +import com.nimbusds.jose.crypto.RSASSASigner; +import com.nimbusds.jose.crypto.RSASSAVerifier; + +public class JWTTokenTest extends org.junit.Assert { + private static final String JWT_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MTY5MjkxMDksImp0aSI6ImFhN2Y4ZDBhOTVjIiwic2NvcGVzIjpbInJlcG8iLCJwdWJsaWNfcmVwbyJdfQ.XCEwpBGvOLma4TCoh36FU7XhUbcskygS81HE1uHLf0E"; + private static final String HEADER = "{\"typ\":\"JWT\",\"alg\":\"HS256\"}"; + - private RSAPublicKey publicKey; - private RSAPrivateKey privateKey; ++ private static RSAPublicKey publicKey; ++ private static RSAPrivateKey privateKey; + - public JWTTokenTest() throws Exception, NoSuchAlgorithmException { ++ @BeforeClass ++ public static void setup() throws Exception, NoSuchAlgorithmException { + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + kpg.initialize(2048); + + KeyPair kp = kpg.genKeyPair(); + publicKey = (RSAPublicKey) kp.getPublic(); + privateKey = (RSAPrivateKey) kp.getPrivate(); + } + + @Test + public void testTokenParsing() throws Exception { + JWTToken token = JWTToken.parseToken(JWT_TOKEN); + assertEquals(token.getHeader(), HEADER); + + assertEquals(token.getClaim("jti"), "aa7f8d0a95c"); + } + + @Test + public void testTokenCreation() throws Exception { + String[] claims = new String[4]; + claims[0] = "KNOXSSO"; + claims[1] = "[email protected]"; + claims[2] = "https://login.example.com"; + claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300); - JWTToken token = new JWTToken("RS256", claims); ++ JWT token = new JWTToken("RS256", claims); + + assertEquals("KNOXSSO", token.getIssuer()); + assertEquals("[email protected]", token.getSubject()); + assertEquals("https://login.example.com", token.getAudience()); + } + + @Test + public void testTokenCreationWithAudienceListSingle() throws Exception { + String[] claims = new String[4]; + claims[0] = "KNOXSSO"; + claims[1] = "[email protected]"; + claims[2] = null; + claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300); - ArrayList<String> audiences = new ArrayList<String>(); ++ List<String> audiences = new ArrayList<String>(); + audiences.add("https://login.example.com"); + - JWTToken token = new JWTToken("RS256", claims, audiences); ++ JWT token = new JWTToken("RS256", claims, audiences); + + assertEquals("KNOXSSO", token.getIssuer()); + assertEquals("[email protected]", token.getSubject()); + assertEquals("https://login.example.com", token.getAudience()); + assertEquals(1, token.getAudienceClaims().length); + } + + @Test + public void testTokenCreationWithAudienceListMultiple() throws Exception { + String[] claims = new String[4]; + claims[0] = "KNOXSSO"; + claims[1] = "[email protected]"; + claims[2] = null; + claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300); - ArrayList<String> audiences = new ArrayList<String>(); ++ List<String> audiences = new ArrayList<String>(); + audiences.add("https://login.example.com"); + audiences.add("KNOXSSO"); + - JWTToken token = new JWTToken("RS256", claims, audiences); ++ JWT token = new JWTToken("RS256", claims, audiences); + + assertEquals("KNOXSSO", token.getIssuer()); + assertEquals("[email protected]", token.getSubject()); + assertEquals("https://login.example.com", token.getAudience()); + assertEquals(2, token.getAudienceClaims().length); + } + + @Test + public void testTokenCreationWithAudienceListCombined() throws Exception { + String[] claims = new String[4]; + claims[0] = "KNOXSSO"; + claims[1] = "[email protected]"; + claims[2] = "LJM"; + claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300); + ArrayList<String> audiences = new ArrayList<String>(); + audiences.add("https://login.example.com"); + audiences.add("KNOXSSO"); + + JWTToken token = new JWTToken("RS256", claims, audiences); + + assertEquals("KNOXSSO", token.getIssuer()); + assertEquals("[email protected]", token.getSubject()); + assertEquals("https://login.example.com", token.getAudience()); + assertEquals(3, token.getAudienceClaims().length); + } + + @Test + public void testTokenCreationWithNullAudienceList() throws Exception { + String[] claims = new String[4]; + claims[0] = "KNOXSSO"; + claims[1] = "[email protected]"; + claims[2] = null; + claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300); - ArrayList<String> audiences = null; ++ List<String> audiences = null; + - JWTToken token = new JWTToken("RS256", claims, audiences); ++ JWT token = new JWTToken("RS256", claims, audiences); + + assertEquals("KNOXSSO", token.getIssuer()); + assertEquals("[email protected]", token.getSubject()); + assertEquals(null, token.getAudience()); + assertArrayEquals(null, token.getAudienceClaims()); + } + + @Test + public void testTokenCreationRS512() throws Exception { + String[] claims = new String[4]; + claims[0] = "KNOXSSO"; + claims[1] = "[email protected]"; + claims[2] = "https://login.example.com"; + claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300); + JWTToken token = new JWTToken(JWSAlgorithm.RS512.getName(), claims); + + assertEquals("KNOXSSO", token.getIssuer()); + assertEquals("[email protected]", token.getSubject()); + assertEquals("https://login.example.com", token.getAudience()); + assertTrue(token.getHeader().contains(JWSAlgorithm.RS512.getName())); + } + + @Test + public void testTokenSignature() throws Exception { + String[] claims = new String[4]; + claims[0] = "KNOXSSO"; + claims[1] = "[email protected]"; + claims[2] = "https://login.example.com"; + claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300); - JWTToken token = new JWTToken("RS256", claims); - ++ JWT token = new JWTToken("RS256", claims); + + assertEquals("KNOXSSO", token.getIssuer()); + assertEquals("[email protected]", token.getSubject()); + assertEquals("https://login.example.com", token.getAudience()); + + // Sign the token + JWSSigner signer = new RSASSASigner(privateKey); + token.sign(signer); + assertTrue(token.getSignaturePayload().length > 0); + + // Verify the signature + JWSVerifier verifier = new RSASSAVerifier((RSAPublicKey) publicKey); + assertTrue(token.verify(verifier)); + } + + @Test + public void testTokenSignatureRS512() throws Exception { + String[] claims = new String[4]; + claims[0] = "KNOXSSO"; + claims[1] = "[email protected]"; + claims[2] = "https://login.example.com"; + claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300); - JWTToken token = new JWTToken(JWSAlgorithm.RS512.getName(), claims); ++ JWT token = new JWTToken(JWSAlgorithm.RS512.getName(), claims); + + assertEquals("KNOXSSO", token.getIssuer()); + assertEquals("[email protected]", token.getSubject()); + assertEquals("https://login.example.com", token.getAudience()); + assertTrue(token.getHeader().contains(JWSAlgorithm.RS512.getName())); + + // Sign the token + JWSSigner signer = new RSASSASigner(privateKey); + token.sign(signer); + assertTrue(token.getSignaturePayload().length > 0); + + // Verify the signature + JWSVerifier verifier = new RSASSAVerifier((RSAPublicKey) publicKey); + assertTrue(token.verify(verifier)); + } + + @Test + public void testTokenExpiry() throws Exception { + String[] claims = new String[4]; + claims[0] = "KNOXSSO"; + claims[1] = "[email protected]"; + claims[2] = "https://login.example.com"; + claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300); - JWTToken token = new JWTToken("RS256", claims); ++ JWT token = new JWTToken("RS256", claims); + + assertNotNull(token.getExpires()); + assertNotNull(token.getExpiresDate()); + assertEquals(token.getExpiresDate(), new Date(Long.valueOf(token.getExpires()))); + } ++ ++ @Test ++ public void testUnsignedToken() throws Exception { ++ String unsignedToken = "eyJhbGciOiJub25lIn0.eyJzdWIiOiJhbGljZSIsImp0aSI6ImY2YmNj" ++ + "MDVjLWI4MTktNGM0Mi1iMGMyLWJlYmY1MDE4YWFiZiJ9."; ++ ++ try { ++ new JWTToken(unsignedToken); ++ fail("Failure expected on an unsigned token"); ++ } catch (ParseException ex) { ++ // expected ++ } ++ } ++ +} http://git-wip-us.apache.org/repos/asf/knox/blob/58780d37/pom.xml ----------------------------------------------------------------------
