Initial commit!

This commit is contained in:
Les Hazlewood 2014-09-18 19:14:22 -07:00
parent f4237320d5
commit 75f1182d17
54 changed files with 6055 additions and 1 deletions

5
.gitignore vendored
View File

@ -10,3 +10,8 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
.idea
*.iml
*.iws

View File

@ -1,4 +1,61 @@
jjwt
====
JSON Web Token library for Java
# JSON Web Token for Java
This library is intended to be the easiest to use and understand library for creating JSON Web Tokens (JWTs) on the JVM, period. Most complexity is hidden behind convenient and readable Builder chaining calls. Here's an example:
//Let's create a random signing key for testing:
Random random = new SecureRandom();
byte[] key = new byte[64];
random.nextBytes(key);
Claims claims = JWTs.claims().setIssuer("Me").setSubject("Joe")
.setExpiration(new Date(System.currentTimeMillis() + 3600));
String jwt = JWTs.builder().setClaims(claims).signWith(SigningAlgorithm.HS256, key).compact();
How easy was that!?
Now let's verify the JWT (you should always discard JWTs that don't match an expected signature):
Token token = JWTs.parser().setSigningKey(key).parse(jwt);
assert token.getClaims().getSubject().equals("Joe");
You have to love one-line code snippets in Java!
But what if signature validation failed? You can catch `SignatureException` and react accordingly:
try {
JWTs.parser().setSigningKey(key).parse(jwt);
//OK, we can trust this JWT
} catch (SignatureException e) {
//don't trust the JWT!
}
## Supported Features
* Creating and parsing plaintext JWTs
* Creating and parsing digitally signed JWTs (aka JWSs) with the following algorithms:
* HS256: HMAC using SHA-384
* HS384: HMAC using SHA-384
* HS512: HMAC using SHA-512
* RS256: RSASSA-PKCS-v1_5 using SHA-256
* RS384: RSASSA-PKCS-v1_5 using SHA-384
* RS512: RSASSA-PKCS-v1_5 using SHA-512
* PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-256
* PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-384
* PS512: RSASSA-PSS using SHA-512 and MGF1 with SHA-512
## Currently Unsupported Features
* Elliptic Curve signature algorithms ES256, ES384 and ES512 are not yet implemented.
* JWE (Encryption for JWT) is not yet implemented.
Both of these feature sets will be implemented in a future release when possible. Community contributions are welcome!

246
pom.xml Normal file
View File

@ -0,0 +1,246 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2014 jsonwebtoken.io
~
~ Licensed 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>7</version>
</parent>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.1-SNAPSHOT</version>
<name>JSON Web Token support for the JVM</name>
<packaging>jar</packaging>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0</url>
<distribution>repo</distribution>
</license>
</licenses>
<scm>
<connection>scm:git:https://github.com/jwtk/jjwt.git</connection>
<developerConnection>scm:git:git@github.com:jwtk/jjwt.git</developerConnection>
<url>git@github.com:jwtk/jjwt.git</url>
<tag>HEAD</tag>
</scm>
<issueManagement>
<system>GitHub Issues</system>
<url>https://github.com/jwtk/jjwt/issues</url>
</issueManagement>
<ciManagement>
<system>TravisCI</system>
<url>https://travis-ci.org/jwtk/jjwt</url>
</ciManagement>
<properties>
<maven.jar.version>2.4</maven.jar.version>
<maven.compiler.version>3.1</maven.compiler.version>
<jdk.version>1.6</jdk.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<buildNumber>${user.name}-${maven.build.timestamp}</buildNumber>
<slf4j.version>1.7.6</slf4j.version>
<jackson.version>2.4.2</jackson.version>
<!-- Optional Runtime Dependencies: -->
<bouncycastle.version>1.51</bouncycastle.version>
<!-- Test Dependencies: Only required for testing when building. Not required by users at runtime: -->
<groovy.version>2.3.0-beta-2</groovy.version>
<logback.version>1.0.7</logback.version>
<easymock.version>3.1</easymock.version>
<testng.version>6.8</testng.version>
<failsafe.plugin.version>2.12.4</failsafe.plugin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!--
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
<scope>runtime</scope>
</dependency>
-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- Optional Dependencies: -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>${bouncycastle.version}</version>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- Test Dependencies: Only required for testing when building. Not required by users at runtime: -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>${testng.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>${groovy.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>${easymock.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.version}</version>
<configuration>
<source>${jdk.version}</source>
<target>${jdk.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven.jar.version}</version>
<configuration>
<archive>
<manifest>
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
</archive>
</configuration>
</plugin>
<!-- Allow for writing tests in Groovy: -->
<plugin>
<groupId>org.codehaus.gmaven</groupId>
<artifactId>gmaven-plugin</artifactId>
<version>1.5</version>
<configuration>
<providerSelection>2.0</providerSelection>
<source/>
</configuration>
<executions>
<execution>
<goals>
<goal>generateStubs</goal>
<goal>compile</goal>
<goal>generateTestStubs</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.codehaus.gmaven.runtime</groupId>
<artifactId>gmaven-runtime-2.0</artifactId>
<version>1.5</version>
<exclusions>
<exclusion>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>${groovy.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.17</version>
<configuration>
<includes>
<include>**/*IT.java</include>
<include>**/*IT.groovy</include>
<include>**/*ITCase.java</include>
<include>**/*ITCase.groovy</include>
</includes>
<excludes>
<exclude>**/*ManualIT.java</exclude>
<exclude>**/*ManualIT.groovy</exclude>
</excludes>
</configuration>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>2.5</version>
<dependencies>
<dependency>
<groupId>org.apache.maven.scm</groupId>
<artifactId>maven-scm-provider-gitexe</artifactId>
<version>1.9</version>
</dependency>
</dependencies>
<configuration>
<mavenExecutorId>forked-path</mavenExecutorId>
<useReleaseProfile>false</useReleaseProfile>
<arguments>${arguments} -Psonatype-oss-release</arguments>
<autoVersionSubmodules>true</autoVersionSubmodules>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,59 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken;
import java.util.Date;
import java.util.Map;
public interface Claims extends Map<String,Object> {
public static final String ISSUER = "iss";
public static final String SUBJECT = "sub";
public static final String AUDIENCE = "aud";
public static final String EXPIRATION = "exp";
public static final String NOT_BEFORE = "nbf";
public static final String ISSUED_AT = "iat";
public static final String ID = "jti";
String getIssuer();
Claims setIssuer(String iss);
String getSubject();
Claims setSubject(String sub);
String getAudience();
Claims setAudience(String aud);
Date getExpiration();
Claims setExpiration(Date exp);
Date getNotBefore();
Claims setNotBefore(Date nbf);
Date getIssuedAt();
Claims setIssuedAt(Date iat);
String getId();
Claims setId(String jti);
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken;
import java.util.Map;
public interface Header extends Map<String,Object> {
public static final String JWT_TYPE = "JWT";
public static final String TYPE = "typ";
public static final String ALGORITHM = "alg";
public static final String CONTENT_TYPE = "cty";
public String getType();
public Header setType(String typ);
public String getAlgorithm();
public Header setAlgorithm(String alg);
public String getContentType();
public void setContentType(String cty);
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken;
import io.jsonwebtoken.impl.DefaultClaims;
import io.jsonwebtoken.impl.DefaultJwtBuilder;
import io.jsonwebtoken.impl.DefaultJwtParser;
import java.util.Map;
public class JWTs {
public static Claims claims() {
return new DefaultClaims();
}
public static Claims claims(Map<String, Object> claims) {
if (claims == null) {
return claims();
}
return new DefaultClaims(claims);
}
public static JwtParser parser() {
return new DefaultJwtParser();
}
public static JwtBuilder builder() {
return new DefaultJwtBuilder();
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken;
import java.security.Key;
import java.util.Map;
public interface JwtBuilder {
//replaces any existing header with the specified header.
JwtBuilder setHeader(Header header);
//replaces current header with specified header
JwtBuilder setHeader(Map<String,Object> header);
//appends to any existing header the specified parameters.
JwtBuilder setHeaderParams(Map<String,Object> params);
//sets the specified header parameter, overwriting any previous value under the same name.
JwtBuilder setHeaderParam(String name, Object value);
JwtBuilder setPayload(String payload);
JwtBuilder setClaims(Claims claims);
JwtBuilder setClaims(Map<String,Object> claims);
JwtBuilder signWith(SignatureAlgorithm alg, byte[] secretKey);
JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey);
JwtBuilder signWith(SignatureAlgorithm alg, Key key);
String compact();
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken;
public class JwtException extends RuntimeException {
public JwtException(String message) {
super(message);
}
public JwtException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken;
import java.security.Key;
public interface JwtParser {
public static final char SEPARATOR_CHAR = '.';
JwtParser setSigningKey(byte[] key);
JwtParser setSigningKey(String base64EncodedKeyBytes);
JwtParser setSigningKey(Key key);
boolean isSigned(String jwt);
Token parse(String jwt) throws MalformedJwtException, SignatureException;
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken;
public class MalformedJwtException extends JwtException {
public MalformedJwtException(String message) {
super(message);
}
public MalformedJwtException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,89 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken;
import io.jsonwebtoken.lang.RuntimeEnvironment;
public enum SignatureAlgorithm {
NONE("none", "No digital signature or MAC performed", null, false),
HS256("HS256", "HMAC using SHA-256", "HmacSHA256", true),
HS384("HS384", "HMAC using SHA-384", "HmacSHA384", true),
HS512("HS512", "HMAC using SHA-512", "HmacSHA512", true),
RS256("RS256", "RSASSA-PKCS-v1_5 using SHA-256", "SHA256withRSA", true),
RS384("RS384", "RSASSA-PKCS-v1_5 using SHA-384", "SHA384withRSA", true),
RS512("RS512", "RSASSA-PKCS-v1_5 using SHA-512", "SHA512withRSA", true),
ES256("ES256", "ECDSA using P-256 and SHA-256", "secp256r1", false), //bouncy castle, not in the jdk
ES384("ES384", "ECDSA using P-384 and SHA-384", "secp384r1", false), //bouncy castle, not in the jdk
ES512("ES512", "ECDSA using P-512 and SHA-512", "secp521r1", false), //bouncy castle, not in the jdk
PS256("PS256", "RSASSA-PSS using SHA-256 and MGF1 with SHA-256", "SHA256withRSAandMGF1", false), //bouncy castle, not in the jdk
PS384("PS384", "RSASSA-PSS using SHA-384 and MGF1 with SHA-384", "SHA384withRSAandMGF1", false), //bouncy castle, not in the jdk
PS512("PS512", "RSASSA-PSS using SHA-512 and MGF1 with SHA-512", "SHA512withRSAandMGF1", false); //bouncy castle, not in the jdk
static {
RuntimeEnvironment.enableBouncyCastleIfPossible();
}
private final String value;
private final String description;
private final String jcaName;
private final boolean jdkStandard;
private SignatureAlgorithm(String value, String description, String jcaName, boolean jdkStandard) {
this.value = value;
this.description = description;
this.jcaName = jcaName;
this.jdkStandard = jdkStandard;
}
public String getValue() {
return value;
}
public String getDescription() {
return description;
}
public String getJcaName() {
return jcaName;
}
public boolean isJdkStandard() {
return jdkStandard;
}
public boolean isHmac() {
return name().startsWith("HS");
}
public boolean isRsa() {
return getDescription().startsWith("RSASSA");
}
public boolean isEllipticCurve() {
return name().startsWith("ES");
}
public static SignatureAlgorithm forName(String value) {
for (SignatureAlgorithm alg : values()) {
if (alg.getValue().equalsIgnoreCase(value)) {
return alg;
}
}
throw new SignatureException("Unsupported signature algorithm '" + value + "'");
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken;
public class SignatureException extends JwtException {
public SignatureException(String message) {
super(message);
}
public SignatureException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,21 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken;
public interface SignedToken<B> extends Token<B> {
String getDigest();
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken;
public interface Token<B> {
Header getHeader();
B getBody();
boolean isSigned();
String getSignature();
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl;
import io.jsonwebtoken.lang.Assert;
import java.nio.charset.Charset;
public abstract class AbstractTextCodec implements TextCodec {
protected static final Charset UTF8 = Charset.forName("UTF-8");
protected static final Charset US_ASCII = Charset.forName("US-ASCII");
@Override
public String encode(String data) {
Assert.hasText(data, "String argument to encode cannot be null or empty.");
byte[] bytes = data.getBytes(UTF8);
return encode(bytes);
}
@Override
public String decodeToString(String encoded) {
byte[] bytes = decode(encoded);
return new String(bytes, UTF8);
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl;
import javax.xml.bind.DatatypeConverter;
public class Base64Codec extends AbstractTextCodec {
@Override
public String encode(byte[] data) {
return DatatypeConverter.printBase64Binary(data);
}
@Override
public byte[] decode(String encoded) {
return DatatypeConverter.parseBase64Binary(encoded);
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl;
public class Base64UrlCodec extends AbstractTextCodec {
@Override
public String encode(byte[] data) {
String base64Text = TextCodec.BASE64.encode(data);
byte[] bytes = base64Text.getBytes(US_ASCII);
//base64url encoding doesn't use padding chars:
bytes = removePadding(bytes);
//replace URL-unfriendly Base64 chars to url-friendly ones:
for (int i = 0; i < bytes.length; i++) {
if (bytes[i] == '+') {
bytes[i] = '-';
} else if (bytes[i] == '/') {
bytes[i] = '_';
}
}
return new String(bytes, US_ASCII);
}
protected byte[] removePadding(byte[] bytes) {
byte[] result = bytes;
int paddingCount = 0;
for (int i = bytes.length - 1; i > 0; i--) {
if (bytes[i] == '=') {
paddingCount++;
} else {
break;
}
}
if (paddingCount > 0) {
result = new byte[bytes.length - paddingCount];
System.arraycopy(bytes, 0, result, 0, bytes.length - paddingCount);
}
return result;
}
@Override
public byte[] decode(String encoded) {
char[] chars = encoded.toCharArray(); //always ASCII - one char == 1 byte
//Base64 requires padding to be in place before decoding, so add it if necessary:
chars = ensurePadding(chars);
//Replace url-friendly chars back to normal Base64 chars:
for (int i = 0; i < chars.length; i++) {
if (chars[i] == '-') {
chars[i] = '+';
} else if (chars[i] == '_') {
chars[i] = '/';
}
}
String base64Text = new String(chars);
return TextCodec.BASE64.decode(base64Text);
}
protected char[] ensurePadding(char[] chars) {
char[] result = chars; //assume argument in case no padding is necessary
int paddingCount = chars.length % 4;
if (paddingCount > 0) {
result = new char[chars.length + paddingCount];
System.arraycopy(chars, 0, result, 0, chars.length);
for (int i = 0; i < paddingCount; i++) {
result[chars.length + i] = '=';
}
}
return result;
}
}

View File

@ -0,0 +1,109 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl;
import io.jsonwebtoken.Claims;
import java.util.Date;
import java.util.Map;
public class DefaultClaims extends JwtMap implements Claims {
public DefaultClaims() {
super();
}
public DefaultClaims(Map<String, Object> map) {
super(map);
}
@Override
public String getIssuer() {
return getString(ISSUER);
}
@Override
public Claims setIssuer(String iss) {
setValue(ISSUER, iss);
return this;
}
@Override
public String getSubject() {
return getString(SUBJECT);
}
@Override
public Claims setSubject(String sub) {
setValue(SUBJECT, sub);
return this;
}
@Override
public String getAudience() {
return getString(AUDIENCE);
}
@Override
public Claims setAudience(String aud) {
setValue(AUDIENCE, aud);
return this;
}
@Override
public Date getExpiration() {
return getDate(Claims.EXPIRATION);
}
@Override
public Claims setExpiration(Date exp) {
setDate(Claims.EXPIRATION, exp);
return this;
}
@Override
public Date getNotBefore() {
return getDate(Claims.NOT_BEFORE);
}
@Override
public Claims setNotBefore(Date nbf) {
setDate(Claims.NOT_BEFORE, nbf);
return this;
}
@Override
public Date getIssuedAt() {
return getDate(Claims.ISSUED_AT);
}
@Override
public Claims setIssuedAt(Date iat) {
setDate(Claims.ISSUED_AT, iat);
return this;
}
@Override
public String getId() {
return getString(ID);
}
@Override
public Claims setId(String jti) {
setValue(Claims.ID, jti);
return this;
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl;
import io.jsonwebtoken.Header;
import java.util.Map;
public class DefaultHeader extends JwtMap implements Header {
public DefaultHeader() {
super();
}
public DefaultHeader(Map<String, Object> map) {
super(map);
}
@Override
public String getType() {
return getString(TYPE);
}
@Override
public Header setType(String typ) {
setValue(TYPE, typ);
return this;
}
@Override
public String getAlgorithm() {
return getString(ALGORITHM);
}
@Override
public Header setAlgorithm(String alg) {
setValue(ALGORITHM, alg);
return this;
}
@Override
public String getContentType() {
return getString(CONTENT_TYPE);
}
@Override
public void setContentType(String cty) {
setValue(CONTENT_TYPE, cty);
}
}

View File

@ -0,0 +1,192 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.JWTs;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.crypto.DefaultJwtSigner;
import io.jsonwebtoken.impl.crypto.JwtSigner;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Objects;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.util.Map;
public class DefaultJwtBuilder implements JwtBuilder {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private Header header;
private Claims claims;
private String payload;
private SignatureAlgorithm algorithm;
private Key key;
private byte[] keyBytes;
@Override
public JwtBuilder setHeader(Header header) {
this.header = header;
return this;
}
@Override
public JwtBuilder setHeader(Map<String, Object> header) {
this.header = new DefaultHeader(header);
return this;
}
@Override
public JwtBuilder setHeaderParams(Map<String, Object> params) {
if (!Collections.isEmpty(params)) {
Header header = ensureHeader();
for (Map.Entry<String, Object> entry : params.entrySet()) {
header.put(entry.getKey(), entry.getValue());
}
}
return this;
}
protected Header ensureHeader() {
if (this.header == null) {
this.header = new DefaultHeader();
}
return this.header;
}
@Override
public JwtBuilder setHeaderParam(String name, Object value) {
ensureHeader().put(name, value);
return this;
}
@Override
public JwtBuilder signWith(SignatureAlgorithm alg, byte[] secretKey) {
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
Assert.notEmpty(secretKey, "secret key byte array cannot be null or empty.");
Assert.isTrue(!alg.isRsa(), "Key bytes cannot be specified for RSA signatures. Please specify an RSA PrivateKey instance.");
this.algorithm = alg;
this.keyBytes = secretKey;
return this;
}
@Override
public JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey) {
Assert.hasText(base64EncodedSecretKey, "base64-encoded secret key cannot be null or empty.");
byte[] bytes = TextCodec.BASE64.decode(base64EncodedSecretKey);
return signWith(alg, bytes);
}
@Override
public JwtBuilder signWith(SignatureAlgorithm alg, Key key) {
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
Assert.notNull(key, "Key argument cannot be null.");
this.algorithm = alg;
this.key = key;
return this;
}
@Override
public JwtBuilder setPayload(String payload) {
this.payload = payload;
return this;
}
@Override
public JwtBuilder setClaims(Claims claims) {
this.claims = claims;
return this;
}
@Override
public JwtBuilder setClaims(Map<String, Object> claims) {
this.claims = JWTs.claims(claims);
return this;
}
@Override
public String compact() {
if (payload == null && claims == null) {
throw new IllegalStateException("Either 'payload' or 'claims' must be specified.");
}
if (payload != null && claims != null) {
throw new IllegalStateException("Both 'payload' and 'claims' cannot both be specified. Choose either one.");
}
if (key != null && keyBytes != null) {
throw new IllegalStateException("A key object and key bytes cannot both be specified. Choose either one.");
}
Header header = ensureHeader();
Key key = this.key;
if (key == null && !Objects.isEmpty(keyBytes)) {
key = new SecretKeySpec(keyBytes, algorithm.getJcaName());
}
if (key != null) {
header.setAlgorithm(algorithm.getValue());
} else {
//no signature - plaintext JWT:
header.setAlgorithm(SignatureAlgorithm.NONE.getValue());
}
String base64UrlEncodedHeader = base64UrlEncode(header, "Unable to serialize header to json.");
String base64UrlEncodedBody = this.payload != null ?
TextCodec.BASE64URL.encode(this.payload) :
base64UrlEncode(claims, "Unable to serialize claims object to json.");
String jwt = base64UrlEncodedHeader + JwtParser.SEPARATOR_CHAR + base64UrlEncodedBody;
if (key != null) { //jwt must be signed:
JwtSigner signer = new DefaultJwtSigner(algorithm, key);
String base64UrlSignature = signer.sign(jwt);
jwt += JwtParser.SEPARATOR_CHAR + base64UrlSignature;
} else {
// no signature (plaintext), but must terminate w/ a period, see
// https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-6.1
jwt += JwtParser.SEPARATOR_CHAR;
}
return jwt;
}
public static String base64UrlEncode(Object o, String errMsg) {
String s;
try {
s = OBJECT_MAPPER.writeValueAsString(o);
} catch (JsonProcessingException e) {
throw new IllegalStateException(errMsg, e);
}
return TextCodec.BASE64URL.encode(s);
}
}

View File

@ -0,0 +1,218 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.Token;
import io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator;
import io.jsonwebtoken.impl.crypto.JwtSignatureValidator;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Objects;
import io.jsonwebtoken.lang.Strings;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.security.Key;
import java.util.Map;
public class DefaultJwtParser implements JwtParser {
private ObjectMapper objectMapper = new ObjectMapper();
private byte[] keyBytes;
private Key key;
@Override
public JwtParser setSigningKey(byte[] key) {
Assert.notEmpty(key, "signing key cannot be null or empty.");
this.keyBytes = key;
return this;
}
@Override
public JwtParser setSigningKey(String base64EncodedKeyBytes) {
Assert.hasText(base64EncodedKeyBytes, "signing key cannot be null or empty.");
this.keyBytes = TextCodec.BASE64.decode(base64EncodedKeyBytes);
return this;
}
@Override
public JwtParser setSigningKey(Key key) {
Assert.notNull(key, "signing key cannot be null.");
this.key = key;
return this;
}
@Override
public boolean isSigned(String jwt) {
if (jwt == null) {
return false;
}
int delimiterCount = 0;
for (int i = 0; i < jwt.length(); i++) {
char c = jwt.charAt(i);
if (delimiterCount == 2) {
return !Character.isWhitespace(c) && c != SEPARATOR_CHAR;
}
if (c == SEPARATOR_CHAR) {
delimiterCount++;
}
}
return false;
}
@Override
public Token parse(String jwt) throws MalformedJwtException, SignatureException {
Assert.hasText(jwt, "JWT String argument cannot be null or empty.");
String base64UrlEncodedHeader = null;
String base64UrlEncodedPayload = null;
String base64UrlEncodedDigest = null;
int delimiterCount = 0;
StringBuilder sb = new StringBuilder(128);
for (char c : jwt.toCharArray()) {
if (c == SEPARATOR_CHAR) {
String token = Strings.clean(sb.toString());
if (delimiterCount == 0) {
base64UrlEncodedHeader = token;
} else if (delimiterCount == 1) {
base64UrlEncodedPayload = token;
}
delimiterCount++;
sb = new StringBuilder(128);
} else {
sb.append(c);
}
}
if (delimiterCount != 2) {
String msg = "JWT strings must contain exactly 2 period characters. Found: " + delimiterCount;
throw new MalformedJwtException(msg);
}
if (sb.length() > 0) {
base64UrlEncodedDigest = sb.toString();
}
if (base64UrlEncodedPayload == null) {
throw new MalformedJwtException("JWT string '" + jwt + "' is missing a body/payload.");
}
// =============== Header =================
Header header = null;
if (base64UrlEncodedHeader != null) {
String origValue = TextCodec.BASE64URL.decodeToString(base64UrlEncodedHeader);
Map<String, Object> m = readValue(origValue);
header = new DefaultHeader(m);
}
// =============== Body =================
String payload = TextCodec.BASE64URL.decodeToString(base64UrlEncodedPayload);
Claims claims = null;
if (payload.charAt(0) == '{' && payload.charAt(payload.length() - 1) == '}') { //likely to be json, parse it:
Map<String, Object> claimsMap = readValue(payload);
claims = new DefaultClaims(claimsMap);
}
// =============== Signature =================
if (base64UrlEncodedDigest != null) { //it is signed - validate the signature
SignatureAlgorithm algorithm = null;
if (header != null) {
String alg = header.getAlgorithm();
if (Strings.hasText(alg)) {
algorithm = SignatureAlgorithm.forName(alg);
}
}
if (algorithm == null || algorithm == SignatureAlgorithm.NONE) {
//it is plaintext, but it has a signature. This is invalid:
String msg = "JWT string has a digest/signature, but the header does not reference a valid signature " +
"algorithm.";
throw new MalformedJwtException(msg);
}
if (key != null && keyBytes != null) {
throw new IllegalStateException("A key object and key bytes cannot both be specified. Choose either.");
}
//digitally signed, let's assert the signature:
Key key = this.key;
if (key == null) { //fall back to keyBytes
if (!Objects.isEmpty(this.keyBytes)) {
Assert.isTrue(!algorithm.isRsa(), "Key bytes cannot be specified for RSA signatures. Please specify a PublicKey or PrivateKey instance.");
key = new SecretKeySpec(keyBytes, algorithm.getJcaName());
}
}
Assert.notNull(key, "A signing key must be specified if the specified JWT is digitally signed.");
//re-create the jwt part without the signature. This is what needs to be signed for verification:
String jwtWithoutSignature = base64UrlEncodedHeader + SEPARATOR_CHAR + base64UrlEncodedPayload;
JwtSignatureValidator validator = new DefaultJwtSignatureValidator(algorithm, key);
if (!validator.isValid(jwtWithoutSignature, base64UrlEncodedDigest)) {
String msg = "JWT signature does not match locally computed signature. JWT validity cannot be " +
"asserted and should not be trusted.";
throw new SignatureException(msg);
}
}
if (claims != null) {
return new DefaultToken<Claims>(header, claims, base64UrlEncodedDigest);
} else {
return new DefaultToken<String>(header, payload, base64UrlEncodedDigest);
}
}
@SuppressWarnings("unchecked")
protected Map<String, Object> readValue(String val) {
try {
return objectMapper.readValue(val, Map.class);
} catch (IOException e) {
throw new MalformedJwtException("Unable to read JSON value: " + val, e);
}
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.Token;
import io.jsonwebtoken.lang.Assert;
public class DefaultToken<B> implements Token<B> {
private final Header header;
private final B body;
private final String signature;
public DefaultToken(Header header, B body, String signature) {
this.header = header;
this.body = body;
this.signature = signature;
}
public boolean hasHeader() {
return this.header != null;
}
public boolean isSigned() {
return this.signature != null;
}
@Override
public String getSignature() {
Assert.notNull(signature, "Not a signed token. Call 'isSigned()' before calling this method.");
return this.signature;
}
@Override
public Header getHeader() {
Assert.notNull(header, "Header is not present. Call 'hasHeader()' before calling this method.");
return header;
}
@Override
public B getBody() {
return body;
}
}

View File

@ -0,0 +1,151 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
public class JwtMap implements Map<String,Object> {
private final Map<String, Object> map;
public JwtMap() {
this(new LinkedHashMap<String, Object>());
}
public JwtMap(Map<String, Object> map) {
if (map == null) {
throw new IllegalArgumentException("Map argument cannot be null.");
}
this.map = map;
}
protected String getString(String name) {
Object v = get(name);
return v != null ? String.valueOf(v) : null;
}
protected static Date toDate(Object v, String name) {
if (v == null) {
return null;
} else if (v instanceof Date) {
return (Date) v;
} else if (v instanceof Number) {
int seconds = ((Number) v).intValue();
return new Date(seconds * 1000);
} else if (v instanceof String) {
int seconds = Integer.parseInt((String) v);
return new Date(seconds * 1000);
} else {
throw new IllegalStateException("Cannot convert '" + name + "' value [" + v + "] to Date instance.");
}
}
protected void setValue(String name, Object v) {
if (v == null) {
map.remove(name);
} else {
map.put(name, v);
}
}
protected Date getDate(String name) {
Object v = map.get(name);
return toDate(v, name);
}
protected void setDate(String name, Date d) {
if (d == null) {
map.remove(name);
} else {
long seconds = d.getTime() / 1000;
map.put(name, seconds);
}
}
@Override
public int size() {
return map.size();
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public boolean containsKey(Object o) {
return map.containsKey(o);
}
@Override
public boolean containsValue(Object o) {
return map.containsValue(o);
}
@Override
public Object get(Object o) {
return map.get(o);
}
@Override
public Object put(String s, Object o) {
if (o == null) {
return map.remove(s);
} else {
return map.put(s, o);
}
}
@Override
public Object remove(Object o) {
return map.remove(o);
}
@SuppressWarnings("NullableProblems")
@Override
public void putAll(Map<? extends String, ?> m) {
if (m == null) {
return;
}
for (String s : m.keySet()) {
map.put(s, m.get(s));
}
}
@Override
public void clear() {
map.clear();
}
@Override
public Set<String> keySet() {
return map.keySet();
}
@Override
public Collection<Object> values() {
return map.values();
}
@Override
public Set<Entry<String, Object>> entrySet() {
return map.entrySet();
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl;
public interface TextCodec {
public static final TextCodec BASE64 = new Base64Codec();
public static final TextCodec BASE64URL = new Base64UrlCodec();
String encode(String data);
String encode(byte[] data);
byte[] decode(String encoded);
String decodeToString(String encoded);
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.lang.Assert;
import java.security.Key;
public abstract class AbstractSigner implements Signer {
protected final SignatureAlgorithm alg;
protected final Key key;
protected AbstractSigner(SignatureAlgorithm alg, Key key) {
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
Assert.notNull(key, "Key cannot be null.");
this.alg = alg;
this.key = key;
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.TextCodec;
import io.jsonwebtoken.lang.Assert;
import java.nio.charset.Charset;
import java.security.Key;
public class DefaultJwtSignatureValidator implements JwtSignatureValidator {
private static final Charset US_ASCII = Charset.forName("US-ASCII");
private final SignatureValidator signatureValidator;
public DefaultJwtSignatureValidator(SignatureAlgorithm alg, Key key) {
this(DefaultSignatureValidatorFactory.INSTANCE, alg, key);
}
public DefaultJwtSignatureValidator(SignatureValidatorFactory factory, SignatureAlgorithm alg, Key key) {
Assert.notNull(factory, "SignerFactory argument cannot be null.");
this.signatureValidator = factory.createSignatureValidator(alg, key);
}
@Override
public boolean isValid(String jwtWithoutSignature, String base64UrlEncodedSignature) {
byte[] data = jwtWithoutSignature.getBytes(US_ASCII);
byte[] signature = TextCodec.BASE64URL.decode(base64UrlEncodedSignature);
return this.signatureValidator.isValid(data, signature);
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.TextCodec;
import io.jsonwebtoken.lang.Assert;
import java.nio.charset.Charset;
import java.security.Key;
public class DefaultJwtSigner implements JwtSigner {
private static final Charset US_ASCII = Charset.forName("US-ASCII");
private final Signer signer;
public DefaultJwtSigner(SignatureAlgorithm alg, Key key) {
this(DefaultSignerFactory.INSTANCE, alg, key);
}
public DefaultJwtSigner(SignerFactory factory, SignatureAlgorithm alg, Key key) {
Assert.notNull(factory, "SignerFactory argument cannot be null.");
this.signer = factory.createSigner(alg, key);
}
@Override
public String sign(String jwtWithoutSignature) {
byte[] bytesToSign = jwtWithoutSignature.getBytes(US_ASCII);
byte[] signature = signer.sign(bytesToSign);
return TextCodec.BASE64URL.encode(signature);
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.lang.Assert;
import java.security.Key;
public class DefaultSignatureValidatorFactory implements SignatureValidatorFactory {
public static final SignatureValidatorFactory INSTANCE = new DefaultSignatureValidatorFactory();
@Override
public SignatureValidator createSignatureValidator(SignatureAlgorithm alg, Key key) {
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
Assert.notNull(key, "Signing Key cannot be null.");
switch (alg) {
case NONE:
throw new IllegalArgumentException("The 'NONE' algorithm cannot be used for signing.");
case HS256:
case HS384:
case HS512:
return new MacValidator(alg, key);
case RS256:
case RS384:
case RS512:
case PS256:
case PS384:
case PS512:
return new RsaSignatureValidator(alg, key);
case ES256:
case ES384:
case ES512:
throw new UnsupportedOperationException("Elliptic Curve digests are not yet supported.");
default:
String msg = "Unrecognized algorithm '" + alg.name() + "'. This is a bug. Please submit a ticket " +
"via the project issue tracker.";
throw new IllegalStateException(msg);
}
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.lang.Assert;
import java.security.Key;
public class DefaultSignerFactory implements SignerFactory {
public static final SignerFactory INSTANCE = new DefaultSignerFactory();
@Override
public Signer createSigner(SignatureAlgorithm alg, Key key) {
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
Assert.notNull(key, "Signing Key cannot be null.");
switch (alg) {
case NONE:
throw new IllegalArgumentException("The 'NONE' algorithm cannot be used for signing.");
case HS256:
case HS384:
case HS512:
return new MacSigner(alg, key);
case RS256:
case RS384:
case RS512:
case PS256:
case PS384:
case PS512:
return new RsaSigner(alg, key);
case ES256:
case ES384:
case ES512:
throw new UnsupportedOperationException("Elliptic Curve digests are not yet supported.");
default:
String msg = "Unrecognized algorithm '" + alg.name() + "'. This is a bug. Please submit a ticket " +
"via the project issue tracker.";
throw new IllegalStateException(msg);
}
}
}

View File

@ -0,0 +1,21 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl.crypto;
public interface JwtSignatureValidator {
boolean isValid(String jwtWithoutSignature, String base64UrlEncodedSignature);
}

View File

@ -0,0 +1,21 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl.crypto;
public interface JwtSigner {
String sign(String jwtWithoutSignature);
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.lang.Assert;
import java.security.Key;
abstract class MacProvider extends SignatureProvider {
protected MacProvider(SignatureAlgorithm alg, Key key) {
super(alg, key);
Assert.isTrue(alg.isHmac(), "SignatureAlgorithm must be a HMAC SHA algorithm.");
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
public class MacSigner extends MacProvider implements Signer {
public MacSigner(SignatureAlgorithm alg, byte[] key) {
this(alg, new SecretKeySpec(key, alg.getJcaName()));
}
public MacSigner(SignatureAlgorithm alg, Key key) {
super(alg, key);
}
@Override
public byte[] sign(byte[] data) {
Mac mac = getMacInstance();
return mac.doFinal(data);
}
protected Mac getMacInstance() throws SignatureException {
try {
return doGetMacInstance();
} catch (NoSuchAlgorithmException e) {
String msg = "Unable to obtain JCA MAC algorithm '" + alg.getJcaName() + "': " + e.getMessage();
throw new SignatureException(msg, e);
} catch (InvalidKeyException e) {
String msg = "The specified signing key is not a valid " + alg.name() + " key: " + e.getMessage();
throw new SignatureException(msg, e);
}
}
protected Mac doGetMacInstance() throws NoSuchAlgorithmException, InvalidKeyException {
Mac mac = Mac.getInstance(alg.getJcaName());
mac.init(key);
return mac;
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
import java.security.Key;
import java.util.Arrays;
public class MacValidator implements SignatureValidator {
private final MacSigner signer;
public MacValidator(SignatureAlgorithm alg, Key key) {
this.signer = new MacSigner(alg, key);
}
@Override
public boolean isValid(byte[] data, byte[] signature) {
byte[] computed = this.signer.sign(data);
return Arrays.equals(computed, signature);
}
}

View File

@ -0,0 +1,92 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.lang.Assert;
import java.security.InvalidAlgorithmParameterException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PSSParameterSpec;
abstract class RsaProvider extends SignatureProvider {
protected RsaProvider(SignatureAlgorithm alg, Key key) {
super(alg, key);
Assert.isTrue(alg.isRsa(), "SignatureAlgorithm must be an RSASSA or RSASSA-PSS algorithm.");
}
protected Signature createSignatureInstance() {
Signature sig = newSignatureInstance();
if (alg.name().startsWith("PS")) {
MGF1ParameterSpec paramSpec;
int saltLength;
switch (alg) {
case PS256:
paramSpec = MGF1ParameterSpec.SHA256;
saltLength = 32;
break;
case PS384:
paramSpec = MGF1ParameterSpec.SHA384;
saltLength = 48;
break;
case PS512:
paramSpec = MGF1ParameterSpec.SHA512;
saltLength = 64;
break;
default:
throw new IllegalArgumentException("Unsupported RSASSA-PSS algorithm: " + alg);
}
PSSParameterSpec pssParamSpec =
new PSSParameterSpec(paramSpec.getDigestAlgorithm(), "MGF1", paramSpec, saltLength, 1);
setParameter(sig, pssParamSpec);
}
return sig;
}
protected Signature newSignatureInstance() {
try {
return Signature.getInstance(alg.getJcaName());
} catch (NoSuchAlgorithmException e) {
String msg = "Unavailable RSA Signature algorithm.";
if (!alg.isJdkStandard()) {
msg += " This is not a standard JDK algorithm. Try including BouncyCastle in the runtime classpath.";
}
throw new SignatureException(msg, e);
}
}
protected void setParameter(Signature sig, PSSParameterSpec spec) {
try {
sig.setParameter(spec);
} catch (InvalidAlgorithmParameterException e) {
String msg = "Unsupported RSASSA-PSS parameter '" + spec + "': " + e.getMessage();
throw new SignatureException(msg, e);
}
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.lang.Assert;
import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.util.Arrays;
public class RsaSignatureValidator extends RsaProvider implements SignatureValidator {
public RsaSignatureValidator(SignatureAlgorithm alg, Key key) {
super(alg, key);
Assert.isTrue(key instanceof PrivateKey || key instanceof PublicKey,
"RSA Signature validation requires either a PublicKey or PrivateKey instance.");
}
@Override
public boolean isValid(byte[] data, byte[] signature) {
if (key instanceof PublicKey) {
Signature sig = createSignatureInstance();
PublicKey publicKey = (PublicKey) key;
try {
sig.initVerify(publicKey);
sig.update(data);
return sig.verify(signature);
} catch (Exception e) {
String msg = "Unable to verify RSA signature using configured PublicKey. " + e.getMessage();
throw new SignatureException(msg, e);
}
} else {
byte[] computed = new RsaSigner(alg, key).sign(data);
return Arrays.equals(computed, signature);
}
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.lang.Assert;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.PrivateKey;
import java.security.Signature;
public class RsaSigner extends RsaProvider implements Signer {
public RsaSigner(SignatureAlgorithm alg, Key key) {
super(alg, key);
Assert.isInstanceOf(PrivateKey.class, key, "RSA signatures be computed using a PrivateKey.");
}
@Override
public byte[] sign(byte[] data) {
try {
return doSign(data);
} catch (InvalidKeyException e) {
throw new SignatureException("Invalid RSA PrivateKey. " + e.getMessage(), e);
} catch (java.security.SignatureException e) {
throw new SignatureException("Unable to calculate signature using RSA PrivateKey. " + e.getMessage(), e);
}
}
protected byte[] doSign(byte[] data) throws InvalidKeyException, java.security.SignatureException {
Assert.isInstanceOf(PrivateKey.class, key, "RSA signatures be computed using a PrivateKey.");
PrivateKey privateKey = (PrivateKey)key;
Signature sig = createSignatureInstance();
sig.initSign(privateKey);
sig.update(data);
return sig.sign();
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.lang.Assert;
import java.security.Key;
abstract class SignatureProvider {
protected final SignatureAlgorithm alg;
protected final Key key;
protected SignatureProvider(SignatureAlgorithm alg, Key key) {
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
Assert.notNull(key, "Key cannot be null.");
this.alg = alg;
this.key = key;
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl.crypto;
public interface SignatureValidator {
boolean isValid(byte[] data, byte[] signature);
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
import java.security.Key;
public interface SignatureValidatorFactory {
SignatureValidator createSignatureValidator(SignatureAlgorithm alg, Key key);
}

View File

@ -0,0 +1,23 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureException;
public interface Signer {
byte[] sign(byte[] data) throws SignatureException;
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
import java.security.Key;
public interface SignerFactory {
Signer createSigner(SignatureAlgorithm alg, Key key);
}

View File

@ -0,0 +1,375 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.lang;
import java.util.Collection;
import java.util.Map;
public abstract class Assert {
/**
* Assert a boolean expression, throwing <code>IllegalArgumentException</code>
* if the test result is <code>false</code>.
* <pre class="code">Assert.isTrue(i &gt; 0, "The value must be greater than zero");</pre>
* @param expression a boolean expression
* @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if expression is <code>false</code>
*/
public static void isTrue(boolean expression, String message) {
if (!expression) {
throw new IllegalArgumentException(message);
}
}
/**
* Assert a boolean expression, throwing <code>IllegalArgumentException</code>
* if the test result is <code>false</code>.
* <pre class="code">Assert.isTrue(i &gt; 0);</pre>
* @param expression a boolean expression
* @throws IllegalArgumentException if expression is <code>false</code>
*/
public static void isTrue(boolean expression) {
isTrue(expression, "[Assertion failed] - this expression must be true");
}
/**
* Assert that an object is <code>null</code> .
* <pre class="code">Assert.isNull(value, "The value must be null");</pre>
* @param object the object to check
* @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if the object is not <code>null</code>
*/
public static void isNull(Object object, String message) {
if (object != null) {
throw new IllegalArgumentException(message);
}
}
/**
* Assert that an object is <code>null</code> .
* <pre class="code">Assert.isNull(value);</pre>
* @param object the object to check
* @throws IllegalArgumentException if the object is not <code>null</code>
*/
public static void isNull(Object object) {
isNull(object, "[Assertion failed] - the object argument must be null");
}
/**
* Assert that an object is not <code>null</code> .
* <pre class="code">Assert.notNull(clazz, "The class must not be null");</pre>
* @param object the object to check
* @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if the object is <code>null</code>
*/
public static void notNull(Object object, String message) {
if (object == null) {
throw new IllegalArgumentException(message);
}
}
/**
* Assert that an object is not <code>null</code> .
* <pre class="code">Assert.notNull(clazz);</pre>
* @param object the object to check
* @throws IllegalArgumentException if the object is <code>null</code>
*/
public static void notNull(Object object) {
notNull(object, "[Assertion failed] - this argument is required; it must not be null");
}
/**
* Assert that the given String is not empty; that is,
* it must not be <code>null</code> and not the empty String.
* <pre class="code">Assert.hasLength(name, "Name must not be empty");</pre>
* @param text the String to check
* @param message the exception message to use if the assertion fails
* @see Strings#hasLength
*/
public static void hasLength(String text, String message) {
if (!Strings.hasLength(text)) {
throw new IllegalArgumentException(message);
}
}
/**
* Assert that the given String is not empty; that is,
* it must not be <code>null</code> and not the empty String.
* <pre class="code">Assert.hasLength(name);</pre>
* @param text the String to check
* @see Strings#hasLength
*/
public static void hasLength(String text) {
hasLength(text,
"[Assertion failed] - this String argument must have length; it must not be null or empty");
}
/**
* Assert that the given String has valid text content; that is, it must not
* be <code>null</code> and must contain at least one non-whitespace character.
* <pre class="code">Assert.hasText(name, "'name' must not be empty");</pre>
* @param text the String to check
* @param message the exception message to use if the assertion fails
* @see Strings#hasText
*/
public static void hasText(String text, String message) {
if (!Strings.hasText(text)) {
throw new IllegalArgumentException(message);
}
}
/**
* Assert that the given String has valid text content; that is, it must not
* be <code>null</code> and must contain at least one non-whitespace character.
* <pre class="code">Assert.hasText(name, "'name' must not be empty");</pre>
* @param text the String to check
* @see Strings#hasText
*/
public static void hasText(String text) {
hasText(text,
"[Assertion failed] - this String argument must have text; it must not be null, empty, or blank");
}
/**
* Assert that the given text does not contain the given substring.
* <pre class="code">Assert.doesNotContain(name, "rod", "Name must not contain 'rod'");</pre>
* @param textToSearch the text to search
* @param substring the substring to find within the text
* @param message the exception message to use if the assertion fails
*/
public static void doesNotContain(String textToSearch, String substring, String message) {
if (Strings.hasLength(textToSearch) && Strings.hasLength(substring) &&
textToSearch.indexOf(substring) != -1) {
throw new IllegalArgumentException(message);
}
}
/**
* Assert that the given text does not contain the given substring.
* <pre class="code">Assert.doesNotContain(name, "rod");</pre>
* @param textToSearch the text to search
* @param substring the substring to find within the text
*/
public static void doesNotContain(String textToSearch, String substring) {
doesNotContain(textToSearch, substring,
"[Assertion failed] - this String argument must not contain the substring [" + substring + "]");
}
/**
* Assert that an array has elements; that is, it must not be
* <code>null</code> and must have at least one element.
* <pre class="code">Assert.notEmpty(array, "The array must have elements");</pre>
* @param array the array to check
* @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if the object array is <code>null</code> or has no elements
*/
public static void notEmpty(Object[] array, String message) {
if (Objects.isEmpty(array)) {
throw new IllegalArgumentException(message);
}
}
/**
* Assert that an array has elements; that is, it must not be
* <code>null</code> and must have at least one element.
* <pre class="code">Assert.notEmpty(array);</pre>
* @param array the array to check
* @throws IllegalArgumentException if the object array is <code>null</code> or has no elements
*/
public static void notEmpty(Object[] array) {
notEmpty(array, "[Assertion failed] - this array must not be empty: it must contain at least 1 element");
}
public static void notEmpty(byte[] array, String msg) {
if (Objects.isEmpty(array)) {
throw new IllegalArgumentException(msg);
}
}
/**
* Assert that an array has no null elements.
* Note: Does not complain if the array is empty!
* <pre class="code">Assert.noNullElements(array, "The array must have non-null elements");</pre>
* @param array the array to check
* @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if the object array contains a <code>null</code> element
*/
public static void noNullElements(Object[] array, String message) {
if (array != null) {
for (int i = 0; i < array.length; i++) {
if (array[i] == null) {
throw new IllegalArgumentException(message);
}
}
}
}
/**
* Assert that an array has no null elements.
* Note: Does not complain if the array is empty!
* <pre class="code">Assert.noNullElements(array);</pre>
* @param array the array to check
* @throws IllegalArgumentException if the object array contains a <code>null</code> element
*/
public static void noNullElements(Object[] array) {
noNullElements(array, "[Assertion failed] - this array must not contain any null elements");
}
/**
* Assert that a collection has elements; that is, it must not be
* <code>null</code> and must have at least one element.
* <pre class="code">Assert.notEmpty(collection, "Collection must have elements");</pre>
* @param collection the collection to check
* @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if the collection is <code>null</code> or has no elements
*/
public static void notEmpty(Collection collection, String message) {
if (Collections.isEmpty(collection)) {
throw new IllegalArgumentException(message);
}
}
/**
* Assert that a collection has elements; that is, it must not be
* <code>null</code> and must have at least one element.
* <pre class="code">Assert.notEmpty(collection, "Collection must have elements");</pre>
* @param collection the collection to check
* @throws IllegalArgumentException if the collection is <code>null</code> or has no elements
*/
public static void notEmpty(Collection collection) {
notEmpty(collection,
"[Assertion failed] - this collection must not be empty: it must contain at least 1 element");
}
/**
* Assert that a Map has entries; that is, it must not be <code>null</code>
* and must have at least one entry.
* <pre class="code">Assert.notEmpty(map, "Map must have entries");</pre>
* @param map the map to check
* @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if the map is <code>null</code> or has no entries
*/
public static void notEmpty(Map map, String message) {
if (Collections.isEmpty(map)) {
throw new IllegalArgumentException(message);
}
}
/**
* Assert that a Map has entries; that is, it must not be <code>null</code>
* and must have at least one entry.
* <pre class="code">Assert.notEmpty(map);</pre>
* @param map the map to check
* @throws IllegalArgumentException if the map is <code>null</code> or has no entries
*/
public static void notEmpty(Map map) {
notEmpty(map, "[Assertion failed] - this map must not be empty; it must contain at least one entry");
}
/**
* Assert that the provided object is an instance of the provided class.
* <pre class="code">Assert.instanceOf(Foo.class, foo);</pre>
* @param clazz the required class
* @param obj the object to check
* @throws IllegalArgumentException if the object is not an instance of clazz
* @see Class#isInstance
*/
public static void isInstanceOf(Class clazz, Object obj) {
isInstanceOf(clazz, obj, "");
}
/**
* Assert that the provided object is an instance of the provided class.
* <pre class="code">Assert.instanceOf(Foo.class, foo);</pre>
* @param type the type to check against
* @param obj the object to check
* @param message a message which will be prepended to the message produced by
* the function itself, and which may be used to provide context. It should
* normally end in a ": " or ". " so that the function generate message looks
* ok when prepended to it.
* @throws IllegalArgumentException if the object is not an instance of clazz
* @see Class#isInstance
*/
public static void isInstanceOf(Class type, Object obj, String message) {
notNull(type, "Type to check against must not be null");
if (!type.isInstance(obj)) {
throw new IllegalArgumentException(message +
"Object of class [" + (obj != null ? obj.getClass().getName() : "null") +
"] must be an instance of " + type);
}
}
/**
* Assert that <code>superType.isAssignableFrom(subType)</code> is <code>true</code>.
* <pre class="code">Assert.isAssignable(Number.class, myClass);</pre>
* @param superType the super type to check
* @param subType the sub type to check
* @throws IllegalArgumentException if the classes are not assignable
*/
public static void isAssignable(Class superType, Class subType) {
isAssignable(superType, subType, "");
}
/**
* Assert that <code>superType.isAssignableFrom(subType)</code> is <code>true</code>.
* <pre class="code">Assert.isAssignable(Number.class, myClass);</pre>
* @param superType the super type to check against
* @param subType the sub type to check
* @param message a message which will be prepended to the message produced by
* the function itself, and which may be used to provide context. It should
* normally end in a ": " or ". " so that the function generate message looks
* ok when prepended to it.
* @throws IllegalArgumentException if the classes are not assignable
*/
public static void isAssignable(Class superType, Class subType, String message) {
notNull(superType, "Type to check against must not be null");
if (subType == null || !superType.isAssignableFrom(subType)) {
throw new IllegalArgumentException(message + subType + " is not assignable to " + superType);
}
}
/**
* Assert a boolean expression, throwing <code>IllegalStateException</code>
* if the test result is <code>false</code>. Call isTrue if you wish to
* throw IllegalArgumentException on an assertion failure.
* <pre class="code">Assert.state(id == null, "The id property must not already be initialized");</pre>
* @param expression a boolean expression
* @param message the exception message to use if the assertion fails
* @throws IllegalStateException if expression is <code>false</code>
*/
public static void state(boolean expression, String message) {
if (!expression) {
throw new IllegalStateException(message);
}
}
/**
* Assert a boolean expression, throwing {@link IllegalStateException}
* if the test result is <code>false</code>.
* <p>Call {@link #isTrue(boolean)} if you wish to
* throw {@link IllegalArgumentException} on an assertion failure.
* <pre class="code">Assert.state(id == null);</pre>
* @param expression a boolean expression
* @throws IllegalStateException if the supplied expression is <code>false</code>
*/
public static void state(boolean expression) {
state(expression, "[Assertion failed] - this state invariant must be true");
}
}

View File

@ -0,0 +1,246 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.lang;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.lang.reflect.Constructor;
/**
* @since 0.1
*/
public class Classes {
/**
* Private internal log instance.
*/
private static final Logger log = LoggerFactory.getLogger(Classes.class);
/**
* @since 0.1
*/
private static final ClassLoaderAccessor THREAD_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
@Override
protected ClassLoader doGetClassLoader() throws Throwable {
return Thread.currentThread().getContextClassLoader();
}
};
/**
* @since 0.1
*/
private static final ClassLoaderAccessor CLASS_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
@Override
protected ClassLoader doGetClassLoader() throws Throwable {
return Classes.class.getClassLoader();
}
};
/**
* @since 0.1
*/
private static final ClassLoaderAccessor SYSTEM_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
@Override
protected ClassLoader doGetClassLoader() throws Throwable {
return ClassLoader.getSystemClassLoader();
}
};
/**
* Attempts to load the specified class name from the current thread's
* {@link Thread#getContextClassLoader() context class loader}, then the
* current ClassLoader (<code>Classes.class.getClassLoader()</code>), then the system/application
* ClassLoader (<code>ClassLoader.getSystemClassLoader()</code>, in that order. If any of them cannot locate
* the specified class, an <code>UnknownClassException</code> is thrown (our RuntimeException equivalent of
* the JRE's <code>ClassNotFoundException</code>.
*
* @param fqcn the fully qualified class name to load
* @return the located class
* @throws UnknownClassException if the class cannot be found.
*/
public static Class forName(String fqcn) throws UnknownClassException {
Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn);
if (clazz == null) {
if (log.isTraceEnabled()) {
log.trace("Unable to load class named [" + fqcn +
"] from the thread context ClassLoader. Trying the current ClassLoader...");
}
clazz = CLASS_CL_ACCESSOR.loadClass(fqcn);
}
if (clazz == null) {
if (log.isTraceEnabled()) {
log.trace("Unable to load class named [" + fqcn + "] from the current ClassLoader. " +
"Trying the system/application ClassLoader...");
}
clazz = SYSTEM_CL_ACCESSOR.loadClass(fqcn);
}
if (clazz == null) {
String msg = "Unable to load class named [" + fqcn + "] from the thread context, current, or " +
"system/application ClassLoaders. All heuristics have been exhausted. Class could not be found.";
if (fqcn != null && fqcn.startsWith("com.stormpath.sdk.impl")) {
msg += " Have you remembered to include the stormpath-sdk-impl .jar in your runtime classpath?";
}
throw new UnknownClassException(msg);
}
return clazz;
}
/**
* Returns the specified resource by checking the current thread's
* {@link Thread#getContextClassLoader() context class loader}, then the
* current ClassLoader (<code>Classes.class.getClassLoader()</code>), then the system/application
* ClassLoader (<code>ClassLoader.getSystemClassLoader()</code>, in that order, using
* {@link ClassLoader#getResourceAsStream(String) getResourceAsStream(name)}.
*
* @param name the name of the resource to acquire from the classloader(s).
* @return the InputStream of the resource found, or <code>null</code> if the resource cannot be found from any
* of the three mentioned ClassLoaders.
* @since 0.8
*/
public static InputStream getResourceAsStream(String name) {
InputStream is = THREAD_CL_ACCESSOR.getResourceStream(name);
if (is == null) {
is = CLASS_CL_ACCESSOR.getResourceStream(name);
}
if (is == null) {
is = SYSTEM_CL_ACCESSOR.getResourceStream(name);
}
return is;
}
public static boolean isAvailable(String fullyQualifiedClassName) {
try {
forName(fullyQualifiedClassName);
return true;
} catch (UnknownClassException e) {
return false;
}
}
@SuppressWarnings("unchecked")
public static Object newInstance(String fqcn) {
return newInstance(forName(fqcn));
}
@SuppressWarnings("unchecked")
public static Object newInstance(String fqcn, Object... args) {
return newInstance(forName(fqcn), args);
}
public static <T> T newInstance(Class<T> clazz) {
if (clazz == null) {
String msg = "Class method parameter cannot be null.";
throw new IllegalArgumentException(msg);
}
try {
return clazz.newInstance();
} catch (Exception e) {
throw new InstantiationException("Unable to instantiate class [" + clazz.getName() + "]", e);
}
}
public static <T> T newInstance(Class<T> clazz, Object... args) {
Class[] argTypes = new Class[args.length];
for (int i = 0; i < args.length; i++) {
argTypes[i] = args[i].getClass();
}
Constructor<T> ctor = getConstructor(clazz, argTypes);
return instantiate(ctor, args);
}
public static <T> Constructor<T> getConstructor(Class<T> clazz, Class... argTypes) {
try {
return clazz.getConstructor(argTypes);
} catch (NoSuchMethodException e) {
throw new IllegalStateException(e);
}
}
public static <T> T instantiate(Constructor<T> ctor, Object... args) {
try {
return ctor.newInstance(args);
} catch (Exception e) {
String msg = "Unable to instantiate instance with constructor [" + ctor + "]";
throw new InstantiationException(msg, e);
}
}
/**
* @since 1.0
*/
private static interface ClassLoaderAccessor {
Class loadClass(String fqcn);
InputStream getResourceStream(String name);
}
/**
* @since 1.0
*/
private static abstract class ExceptionIgnoringAccessor implements ClassLoaderAccessor {
public Class loadClass(String fqcn) {
Class clazz = null;
ClassLoader cl = getClassLoader();
if (cl != null) {
try {
clazz = cl.loadClass(fqcn);
} catch (ClassNotFoundException e) {
if (log.isTraceEnabled()) {
log.trace("Unable to load clazz named [" + fqcn + "] from class loader [" + cl + "]");
}
}
}
return clazz;
}
public InputStream getResourceStream(String name) {
InputStream is = null;
ClassLoader cl = getClassLoader();
if (cl != null) {
is = cl.getResourceAsStream(name);
}
return is;
}
protected final ClassLoader getClassLoader() {
try {
return doGetClassLoader();
} catch (Throwable t) {
if (log.isDebugEnabled()) {
log.debug("Unable to acquire ClassLoader.", t);
}
}
return null;
}
protected abstract ClassLoader doGetClassLoader() throws Throwable;
}
}

View File

@ -0,0 +1,363 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.lang;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
public abstract class Collections {
/**
* Return <code>true</code> if the supplied Collection is <code>null</code>
* or empty. Otherwise, return <code>false</code>.
* @param collection the Collection to check
* @return whether the given Collection is empty
*/
public static boolean isEmpty(Collection collection) {
return (collection == null || collection.isEmpty());
}
/**
* Returns the collection's size or {@code 0} if the collection is {@code null}.
*
* @param collection the collection to check.
* @return the collection's size or {@code 0} if the collection is {@code null}.
* @since 0.9.2
*/
public static int size(Collection collection) {
return collection == null ? 0 : collection.size();
}
/**
* Returns the map's size or {@code 0} if the map is {@code null}.
*
* @param map the map to check
* @return the map's size or {@code 0} if the map is {@code null}.
* @since 0.9.2
*/
public static int size(Map map) {
return map == null ? 0 : map.size();
}
/**
* Return <code>true</code> if the supplied Map is <code>null</code>
* or empty. Otherwise, return <code>false</code>.
* @param map the Map to check
* @return whether the given Map is empty
*/
public static boolean isEmpty(Map map) {
return (map == null || map.isEmpty());
}
/**
* Convert the supplied array into a List. A primitive array gets
* converted into a List of the appropriate wrapper type.
* <p>A <code>null</code> source value will be converted to an
* empty List.
* @param source the (potentially primitive) array
* @return the converted List result
* @see Objects#toObjectArray(Object)
*/
public static List arrayToList(Object source) {
return Arrays.asList(Objects.toObjectArray(source));
}
/**
* Merge the given array into the given Collection.
* @param array the array to merge (may be <code>null</code>)
* @param collection the target Collection to merge the array into
*/
@SuppressWarnings("unchecked")
public static void mergeArrayIntoCollection(Object array, Collection collection) {
if (collection == null) {
throw new IllegalArgumentException("Collection must not be null");
}
Object[] arr = Objects.toObjectArray(array);
for (Object elem : arr) {
collection.add(elem);
}
}
/**
* Merge the given Properties instance into the given Map,
* copying all properties (key-value pairs) over.
* <p>Uses <code>Properties.propertyNames()</code> to even catch
* default properties linked into the original Properties instance.
* @param props the Properties instance to merge (may be <code>null</code>)
* @param map the target Map to merge the properties into
*/
@SuppressWarnings("unchecked")
public static void mergePropertiesIntoMap(Properties props, Map map) {
if (map == null) {
throw new IllegalArgumentException("Map must not be null");
}
if (props != null) {
for (Enumeration en = props.propertyNames(); en.hasMoreElements();) {
String key = (String) en.nextElement();
Object value = props.getProperty(key);
if (value == null) {
// Potentially a non-String value...
value = props.get(key);
}
map.put(key, value);
}
}
}
/**
* Check whether the given Iterator contains the given element.
* @param iterator the Iterator to check
* @param element the element to look for
* @return <code>true</code> if found, <code>false</code> else
*/
public static boolean contains(Iterator iterator, Object element) {
if (iterator != null) {
while (iterator.hasNext()) {
Object candidate = iterator.next();
if (Objects.nullSafeEquals(candidate, element)) {
return true;
}
}
}
return false;
}
/**
* Check whether the given Enumeration contains the given element.
* @param enumeration the Enumeration to check
* @param element the element to look for
* @return <code>true</code> if found, <code>false</code> else
*/
public static boolean contains(Enumeration enumeration, Object element) {
if (enumeration != null) {
while (enumeration.hasMoreElements()) {
Object candidate = enumeration.nextElement();
if (Objects.nullSafeEquals(candidate, element)) {
return true;
}
}
}
return false;
}
/**
* Check whether the given Collection contains the given element instance.
* <p>Enforces the given instance to be present, rather than returning
* <code>true</code> for an equal element as well.
* @param collection the Collection to check
* @param element the element to look for
* @return <code>true</code> if found, <code>false</code> else
*/
public static boolean containsInstance(Collection collection, Object element) {
if (collection != null) {
for (Object candidate : collection) {
if (candidate == element) {
return true;
}
}
}
return false;
}
/**
* Return <code>true</code> if any element in '<code>candidates</code>' is
* contained in '<code>source</code>'; otherwise returns <code>false</code>.
* @param source the source Collection
* @param candidates the candidates to search for
* @return whether any of the candidates has been found
*/
public static boolean containsAny(Collection source, Collection candidates) {
if (isEmpty(source) || isEmpty(candidates)) {
return false;
}
for (Object candidate : candidates) {
if (source.contains(candidate)) {
return true;
}
}
return false;
}
/**
* Return the first element in '<code>candidates</code>' that is contained in
* '<code>source</code>'. If no element in '<code>candidates</code>' is present in
* '<code>source</code>' returns <code>null</code>. Iteration order is
* {@link Collection} implementation specific.
* @param source the source Collection
* @param candidates the candidates to search for
* @return the first present object, or <code>null</code> if not found
*/
public static Object findFirstMatch(Collection source, Collection candidates) {
if (isEmpty(source) || isEmpty(candidates)) {
return null;
}
for (Object candidate : candidates) {
if (source.contains(candidate)) {
return candidate;
}
}
return null;
}
/**
* Find a single value of the given type in the given Collection.
* @param collection the Collection to search
* @param type the type to look for
* @return a value of the given type found if there is a clear match,
* or <code>null</code> if none or more than one such value found
*/
@SuppressWarnings("unchecked")
public static <T> T findValueOfType(Collection<?> collection, Class<T> type) {
if (isEmpty(collection)) {
return null;
}
T value = null;
for (Object element : collection) {
if (type == null || type.isInstance(element)) {
if (value != null) {
// More than one value found... no clear single value.
return null;
}
value = (T) element;
}
}
return value;
}
/**
* Find a single value of one of the given types in the given Collection:
* searching the Collection for a value of the first type, then
* searching for a value of the second type, etc.
* @param collection the collection to search
* @param types the types to look for, in prioritized order
* @return a value of one of the given types found if there is a clear match,
* or <code>null</code> if none or more than one such value found
*/
public static Object findValueOfType(Collection<?> collection, Class<?>[] types) {
if (isEmpty(collection) || Objects.isEmpty(types)) {
return null;
}
for (Class<?> type : types) {
Object value = findValueOfType(collection, type);
if (value != null) {
return value;
}
}
return null;
}
/**
* Determine whether the given Collection only contains a single unique object.
* @param collection the Collection to check
* @return <code>true</code> if the collection contains a single reference or
* multiple references to the same instance, <code>false</code> else
*/
public static boolean hasUniqueObject(Collection collection) {
if (isEmpty(collection)) {
return false;
}
boolean hasCandidate = false;
Object candidate = null;
for (Object elem : collection) {
if (!hasCandidate) {
hasCandidate = true;
candidate = elem;
}
else if (candidate != elem) {
return false;
}
}
return true;
}
/**
* Find the common element type of the given Collection, if any.
* @param collection the Collection to check
* @return the common element type, or <code>null</code> if no clear
* common type has been found (or the collection was empty)
*/
public static Class<?> findCommonElementType(Collection collection) {
if (isEmpty(collection)) {
return null;
}
Class<?> candidate = null;
for (Object val : collection) {
if (val != null) {
if (candidate == null) {
candidate = val.getClass();
}
else if (candidate != val.getClass()) {
return null;
}
}
}
return candidate;
}
/**
* Marshal the elements from the given enumeration into an array of the given type.
* Enumeration elements must be assignable to the type of the given array. The array
* returned will be a different instance than the array given.
*/
public static <A,E extends A> A[] toArray(Enumeration<E> enumeration, A[] array) {
ArrayList<A> elements = new ArrayList<A>();
while (enumeration.hasMoreElements()) {
elements.add(enumeration.nextElement());
}
return elements.toArray(array);
}
/**
* Adapt an enumeration to an iterator.
* @param enumeration the enumeration
* @return the iterator
*/
public static <E> Iterator<E> toIterator(Enumeration<E> enumeration) {
return new EnumerationIterator<E>(enumeration);
}
/**
* Iterator wrapping an Enumeration.
*/
private static class EnumerationIterator<E> implements Iterator<E> {
private Enumeration<E> enumeration;
public EnumerationIterator(Enumeration<E> enumeration) {
this.enumeration = enumeration;
}
public boolean hasNext() {
return this.enumeration.hasMoreElements();
}
public E next() {
return this.enumeration.nextElement();
}
public void remove() throws UnsupportedOperationException {
throw new UnsupportedOperationException("Not supported");
}
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.lang;
/**
* @since 0.1
*/
public class InstantiationException extends RuntimeException {
public InstantiationException(String s, Throwable t) {
super(s, t);
}
}

View File

@ -0,0 +1,908 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.lang;
import java.lang.reflect.Array;
import java.util.Arrays;
public abstract class Objects {
private static final int INITIAL_HASH = 7;
private static final int MULTIPLIER = 31;
private static final String EMPTY_STRING = "";
private static final String NULL_STRING = "null";
private static final String ARRAY_START = "{";
private static final String ARRAY_END = "}";
private static final String EMPTY_ARRAY = ARRAY_START + ARRAY_END;
private static final String ARRAY_ELEMENT_SEPARATOR = ", ";
/**
* Return whether the given throwable is a checked exception:
* that is, neither a RuntimeException nor an Error.
*
* @param ex the throwable to check
* @return whether the throwable is a checked exception
* @see java.lang.Exception
* @see java.lang.RuntimeException
* @see java.lang.Error
*/
public static boolean isCheckedException(Throwable ex) {
return !(ex instanceof RuntimeException || ex instanceof Error);
}
/**
* Check whether the given exception is compatible with the exceptions
* declared in a throws clause.
*
* @param ex the exception to checked
* @param declaredExceptions the exceptions declared in the throws clause
* @return whether the given exception is compatible
*/
public static boolean isCompatibleWithThrowsClause(Throwable ex, Class[] declaredExceptions) {
if (!isCheckedException(ex)) {
return true;
}
if (declaredExceptions != null) {
int i = 0;
while (i < declaredExceptions.length) {
if (declaredExceptions[i].isAssignableFrom(ex.getClass())) {
return true;
}
i++;
}
}
return false;
}
/**
* Determine whether the given object is an array:
* either an Object array or a primitive array.
*
* @param obj the object to check
*/
public static boolean isArray(Object obj) {
return (obj != null && obj.getClass().isArray());
}
/**
* Determine whether the given array is empty:
* i.e. <code>null</code> or of zero length.
*
* @param array the array to check
*/
public static boolean isEmpty(Object[] array) {
return (array == null || array.length == 0);
}
/**
* Returns {@code true} if the specified byte array is null or of zero length, {@code false} otherwise.
*
* @param array the byte array to check
* @return {@code true} if the specified byte array is null or of zero length, {@code false} otherwise.
*/
public static boolean isEmpty(byte[] array) {
return array == null || array.length == 0;
}
/**
* Check whether the given array contains the given element.
*
* @param array the array to check (may be <code>null</code>,
* in which case the return value will always be <code>false</code>)
* @param element the element to check for
* @return whether the element has been found in the given array
*/
public static boolean containsElement(Object[] array, Object element) {
if (array == null) {
return false;
}
for (Object arrayEle : array) {
if (nullSafeEquals(arrayEle, element)) {
return true;
}
}
return false;
}
/**
* Check whether the given array of enum constants contains a constant with the given name,
* ignoring case when determining a match.
*
* @param enumValues the enum values to check, typically the product of a call to MyEnum.values()
* @param constant the constant name to find (must not be null or empty string)
* @return whether the constant has been found in the given array
*/
public static boolean containsConstant(Enum<?>[] enumValues, String constant) {
return containsConstant(enumValues, constant, false);
}
/**
* Check whether the given array of enum constants contains a constant with the given name.
*
* @param enumValues the enum values to check, typically the product of a call to MyEnum.values()
* @param constant the constant name to find (must not be null or empty string)
* @param caseSensitive whether case is significant in determining a match
* @return whether the constant has been found in the given array
*/
public static boolean containsConstant(Enum<?>[] enumValues, String constant, boolean caseSensitive) {
for (Enum<?> candidate : enumValues) {
if (caseSensitive ?
candidate.toString().equals(constant) :
candidate.toString().equalsIgnoreCase(constant)) {
return true;
}
}
return false;
}
/**
* Case insensitive alternative to {@link Enum#valueOf(Class, String)}.
*
* @param <E> the concrete Enum type
* @param enumValues the array of all Enum constants in question, usually per Enum.values()
* @param constant the constant to get the enum value of
* @throws IllegalArgumentException if the given constant is not found in the given array
* of enum values. Use {@link #containsConstant(Enum[], String)} as a guard to
* avoid this exception.
*/
public static <E extends Enum<?>> E caseInsensitiveValueOf(E[] enumValues, String constant) {
for (E candidate : enumValues) {
if (candidate.toString().equalsIgnoreCase(constant)) {
return candidate;
}
}
throw new IllegalArgumentException(
String.format("constant [%s] does not exist in enum type %s",
constant, enumValues.getClass().getComponentType().getName()));
}
/**
* Append the given object to the given array, returning a new array
* consisting of the input array contents plus the given object.
*
* @param array the array to append to (can be <code>null</code>)
* @param obj the object to append
* @return the new array (of the same component type; never <code>null</code>)
*/
public static <A, O extends A> A[] addObjectToArray(A[] array, O obj) {
Class<?> compType = Object.class;
if (array != null) {
compType = array.getClass().getComponentType();
} else if (obj != null) {
compType = obj.getClass();
}
int newArrLength = (array != null ? array.length + 1 : 1);
@SuppressWarnings("unchecked")
A[] newArr = (A[]) Array.newInstance(compType, newArrLength);
if (array != null) {
System.arraycopy(array, 0, newArr, 0, array.length);
}
newArr[newArr.length - 1] = obj;
return newArr;
}
/**
* Convert the given array (which may be a primitive array) to an
* object array (if necessary of primitive wrapper objects).
* <p>A <code>null</code> source value will be converted to an
* empty Object array.
*
* @param source the (potentially primitive) array
* @return the corresponding object array (never <code>null</code>)
* @throws IllegalArgumentException if the parameter is not an array
*/
public static Object[] toObjectArray(Object source) {
if (source instanceof Object[]) {
return (Object[]) source;
}
if (source == null) {
return new Object[0];
}
if (!source.getClass().isArray()) {
throw new IllegalArgumentException("Source is not an array: " + source);
}
int length = Array.getLength(source);
if (length == 0) {
return new Object[0];
}
Class wrapperType = Array.get(source, 0).getClass();
Object[] newArray = (Object[]) Array.newInstance(wrapperType, length);
for (int i = 0; i < length; i++) {
newArray[i] = Array.get(source, i);
}
return newArray;
}
//---------------------------------------------------------------------
// Convenience methods for content-based equality/hash-code handling
//---------------------------------------------------------------------
/**
* Determine if the given objects are equal, returning <code>true</code>
* if both are <code>null</code> or <code>false</code> if only one is
* <code>null</code>.
* <p>Compares arrays with <code>Arrays.equals</code>, performing an equality
* check based on the array elements rather than the array reference.
*
* @param o1 first Object to compare
* @param o2 second Object to compare
* @return whether the given objects are equal
* @see java.util.Arrays#equals
*/
public static boolean nullSafeEquals(Object o1, Object o2) {
if (o1 == o2) {
return true;
}
if (o1 == null || o2 == null) {
return false;
}
if (o1.equals(o2)) {
return true;
}
if (o1.getClass().isArray() && o2.getClass().isArray()) {
if (o1 instanceof Object[] && o2 instanceof Object[]) {
return Arrays.equals((Object[]) o1, (Object[]) o2);
}
if (o1 instanceof boolean[] && o2 instanceof boolean[]) {
return Arrays.equals((boolean[]) o1, (boolean[]) o2);
}
if (o1 instanceof byte[] && o2 instanceof byte[]) {
return Arrays.equals((byte[]) o1, (byte[]) o2);
}
if (o1 instanceof char[] && o2 instanceof char[]) {
return Arrays.equals((char[]) o1, (char[]) o2);
}
if (o1 instanceof double[] && o2 instanceof double[]) {
return Arrays.equals((double[]) o1, (double[]) o2);
}
if (o1 instanceof float[] && o2 instanceof float[]) {
return Arrays.equals((float[]) o1, (float[]) o2);
}
if (o1 instanceof int[] && o2 instanceof int[]) {
return Arrays.equals((int[]) o1, (int[]) o2);
}
if (o1 instanceof long[] && o2 instanceof long[]) {
return Arrays.equals((long[]) o1, (long[]) o2);
}
if (o1 instanceof short[] && o2 instanceof short[]) {
return Arrays.equals((short[]) o1, (short[]) o2);
}
}
return false;
}
/**
* Return as hash code for the given object; typically the value of
* <code>{@link Object#hashCode()}</code>. If the object is an array,
* this method will delegate to any of the <code>nullSafeHashCode</code>
* methods for arrays in this class. If the object is <code>null</code>,
* this method returns 0.
*
* @see #nullSafeHashCode(Object[])
* @see #nullSafeHashCode(boolean[])
* @see #nullSafeHashCode(byte[])
* @see #nullSafeHashCode(char[])
* @see #nullSafeHashCode(double[])
* @see #nullSafeHashCode(float[])
* @see #nullSafeHashCode(int[])
* @see #nullSafeHashCode(long[])
* @see #nullSafeHashCode(short[])
*/
public static int nullSafeHashCode(Object obj) {
if (obj == null) {
return 0;
}
if (obj.getClass().isArray()) {
if (obj instanceof Object[]) {
return nullSafeHashCode((Object[]) obj);
}
if (obj instanceof boolean[]) {
return nullSafeHashCode((boolean[]) obj);
}
if (obj instanceof byte[]) {
return nullSafeHashCode((byte[]) obj);
}
if (obj instanceof char[]) {
return nullSafeHashCode((char[]) obj);
}
if (obj instanceof double[]) {
return nullSafeHashCode((double[]) obj);
}
if (obj instanceof float[]) {
return nullSafeHashCode((float[]) obj);
}
if (obj instanceof int[]) {
return nullSafeHashCode((int[]) obj);
}
if (obj instanceof long[]) {
return nullSafeHashCode((long[]) obj);
}
if (obj instanceof short[]) {
return nullSafeHashCode((short[]) obj);
}
}
return obj.hashCode();
}
/**
* Return a hash code based on the contents of the specified array.
* If <code>array</code> is <code>null</code>, this method returns 0.
*/
public static int nullSafeHashCode(Object[] array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
int arraySize = array.length;
for (int i = 0; i < arraySize; i++) {
hash = MULTIPLIER * hash + nullSafeHashCode(array[i]);
}
return hash;
}
/**
* Return a hash code based on the contents of the specified array.
* If <code>array</code> is <code>null</code>, this method returns 0.
*/
public static int nullSafeHashCode(boolean[] array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
int arraySize = array.length;
for (int i = 0; i < arraySize; i++) {
hash = MULTIPLIER * hash + hashCode(array[i]);
}
return hash;
}
/**
* Return a hash code based on the contents of the specified array.
* If <code>array</code> is <code>null</code>, this method returns 0.
*/
public static int nullSafeHashCode(byte[] array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
int arraySize = array.length;
for (int i = 0; i < arraySize; i++) {
hash = MULTIPLIER * hash + array[i];
}
return hash;
}
/**
* Return a hash code based on the contents of the specified array.
* If <code>array</code> is <code>null</code>, this method returns 0.
*/
public static int nullSafeHashCode(char[] array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
int arraySize = array.length;
for (int i = 0; i < arraySize; i++) {
hash = MULTIPLIER * hash + array[i];
}
return hash;
}
/**
* Return a hash code based on the contents of the specified array.
* If <code>array</code> is <code>null</code>, this method returns 0.
*/
public static int nullSafeHashCode(double[] array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
int arraySize = array.length;
for (int i = 0; i < arraySize; i++) {
hash = MULTIPLIER * hash + hashCode(array[i]);
}
return hash;
}
/**
* Return a hash code based on the contents of the specified array.
* If <code>array</code> is <code>null</code>, this method returns 0.
*/
public static int nullSafeHashCode(float[] array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
int arraySize = array.length;
for (int i = 0; i < arraySize; i++) {
hash = MULTIPLIER * hash + hashCode(array[i]);
}
return hash;
}
/**
* Return a hash code based on the contents of the specified array.
* If <code>array</code> is <code>null</code>, this method returns 0.
*/
public static int nullSafeHashCode(int[] array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
int arraySize = array.length;
for (int i = 0; i < arraySize; i++) {
hash = MULTIPLIER * hash + array[i];
}
return hash;
}
/**
* Return a hash code based on the contents of the specified array.
* If <code>array</code> is <code>null</code>, this method returns 0.
*/
public static int nullSafeHashCode(long[] array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
int arraySize = array.length;
for (int i = 0; i < arraySize; i++) {
hash = MULTIPLIER * hash + hashCode(array[i]);
}
return hash;
}
/**
* Return a hash code based on the contents of the specified array.
* If <code>array</code> is <code>null</code>, this method returns 0.
*/
public static int nullSafeHashCode(short[] array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
int arraySize = array.length;
for (int i = 0; i < arraySize; i++) {
hash = MULTIPLIER * hash + array[i];
}
return hash;
}
/**
* Return the same value as <code>{@link Boolean#hashCode()}</code>.
*
* @see Boolean#hashCode()
*/
public static int hashCode(boolean bool) {
return bool ? 1231 : 1237;
}
/**
* Return the same value as <code>{@link Double#hashCode()}</code>.
*
* @see Double#hashCode()
*/
public static int hashCode(double dbl) {
long bits = Double.doubleToLongBits(dbl);
return hashCode(bits);
}
/**
* Return the same value as <code>{@link Float#hashCode()}</code>.
*
* @see Float#hashCode()
*/
public static int hashCode(float flt) {
return Float.floatToIntBits(flt);
}
/**
* Return the same value as <code>{@link Long#hashCode()}</code>.
*
* @see Long#hashCode()
*/
public static int hashCode(long lng) {
return (int) (lng ^ (lng >>> 32));
}
//---------------------------------------------------------------------
// Convenience methods for toString output
//---------------------------------------------------------------------
/**
* Return a String representation of an object's overall identity.
*
* @param obj the object (may be <code>null</code>)
* @return the object's identity as String representation,
* or an empty String if the object was <code>null</code>
*/
public static String identityToString(Object obj) {
if (obj == null) {
return EMPTY_STRING;
}
return obj.getClass().getName() + "@" + getIdentityHexString(obj);
}
/**
* Return a hex String form of an object's identity hash code.
*
* @param obj the object
* @return the object's identity code in hex notation
*/
public static String getIdentityHexString(Object obj) {
return Integer.toHexString(System.identityHashCode(obj));
}
/**
* Return a content-based String representation if <code>obj</code> is
* not <code>null</code>; otherwise returns an empty String.
* <p>Differs from {@link #nullSafeToString(Object)} in that it returns
* an empty String rather than "null" for a <code>null</code> value.
*
* @param obj the object to build a display String for
* @return a display String representation of <code>obj</code>
* @see #nullSafeToString(Object)
*/
public static String getDisplayString(Object obj) {
if (obj == null) {
return EMPTY_STRING;
}
return nullSafeToString(obj);
}
/**
* Determine the class name for the given object.
* <p>Returns <code>"null"</code> if <code>obj</code> is <code>null</code>.
*
* @param obj the object to introspect (may be <code>null</code>)
* @return the corresponding class name
*/
public static String nullSafeClassName(Object obj) {
return (obj != null ? obj.getClass().getName() : NULL_STRING);
}
/**
* Return a String representation of the specified Object.
* <p>Builds a String representation of the contents in case of an array.
* Returns <code>"null"</code> if <code>obj</code> is <code>null</code>.
*
* @param obj the object to build a String representation for
* @return a String representation of <code>obj</code>
*/
public static String nullSafeToString(Object obj) {
if (obj == null) {
return NULL_STRING;
}
if (obj instanceof String) {
return (String) obj;
}
if (obj instanceof Object[]) {
return nullSafeToString((Object[]) obj);
}
if (obj instanceof boolean[]) {
return nullSafeToString((boolean[]) obj);
}
if (obj instanceof byte[]) {
return nullSafeToString((byte[]) obj);
}
if (obj instanceof char[]) {
return nullSafeToString((char[]) obj);
}
if (obj instanceof double[]) {
return nullSafeToString((double[]) obj);
}
if (obj instanceof float[]) {
return nullSafeToString((float[]) obj);
}
if (obj instanceof int[]) {
return nullSafeToString((int[]) obj);
}
if (obj instanceof long[]) {
return nullSafeToString((long[]) obj);
}
if (obj instanceof short[]) {
return nullSafeToString((short[]) obj);
}
String str = obj.toString();
return (str != null ? str : EMPTY_STRING);
}
/**
* Return a String representation of the contents of the specified array.
* <p>The String representation consists of a list of the array's elements,
* enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated
* by the characters <code>", "</code> (a comma followed by a space). Returns
* <code>"null"</code> if <code>array</code> is <code>null</code>.
*
* @param array the array to build a String representation for
* @return a String representation of <code>array</code>
*/
public static String nullSafeToString(Object[] array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
} else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append(String.valueOf(array[i]));
}
sb.append(ARRAY_END);
return sb.toString();
}
/**
* Return a String representation of the contents of the specified array.
* <p>The String representation consists of a list of the array's elements,
* enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated
* by the characters <code>", "</code> (a comma followed by a space). Returns
* <code>"null"</code> if <code>array</code> is <code>null</code>.
*
* @param array the array to build a String representation for
* @return a String representation of <code>array</code>
*/
public static String nullSafeToString(boolean[] array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
} else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append(array[i]);
}
sb.append(ARRAY_END);
return sb.toString();
}
/**
* Return a String representation of the contents of the specified array.
* <p>The String representation consists of a list of the array's elements,
* enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated
* by the characters <code>", "</code> (a comma followed by a space). Returns
* <code>"null"</code> if <code>array</code> is <code>null</code>.
*
* @param array the array to build a String representation for
* @return a String representation of <code>array</code>
*/
public static String nullSafeToString(byte[] array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
} else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append(array[i]);
}
sb.append(ARRAY_END);
return sb.toString();
}
/**
* Return a String representation of the contents of the specified array.
* <p>The String representation consists of a list of the array's elements,
* enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated
* by the characters <code>", "</code> (a comma followed by a space). Returns
* <code>"null"</code> if <code>array</code> is <code>null</code>.
*
* @param array the array to build a String representation for
* @return a String representation of <code>array</code>
*/
public static String nullSafeToString(char[] array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
} else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append("'").append(array[i]).append("'");
}
sb.append(ARRAY_END);
return sb.toString();
}
/**
* Return a String representation of the contents of the specified array.
* <p>The String representation consists of a list of the array's elements,
* enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated
* by the characters <code>", "</code> (a comma followed by a space). Returns
* <code>"null"</code> if <code>array</code> is <code>null</code>.
*
* @param array the array to build a String representation for
* @return a String representation of <code>array</code>
*/
public static String nullSafeToString(double[] array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
} else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append(array[i]);
}
sb.append(ARRAY_END);
return sb.toString();
}
/**
* Return a String representation of the contents of the specified array.
* <p>The String representation consists of a list of the array's elements,
* enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated
* by the characters <code>", "</code> (a comma followed by a space). Returns
* <code>"null"</code> if <code>array</code> is <code>null</code>.
*
* @param array the array to build a String representation for
* @return a String representation of <code>array</code>
*/
public static String nullSafeToString(float[] array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
} else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append(array[i]);
}
sb.append(ARRAY_END);
return sb.toString();
}
/**
* Return a String representation of the contents of the specified array.
* <p>The String representation consists of a list of the array's elements,
* enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated
* by the characters <code>", "</code> (a comma followed by a space). Returns
* <code>"null"</code> if <code>array</code> is <code>null</code>.
*
* @param array the array to build a String representation for
* @return a String representation of <code>array</code>
*/
public static String nullSafeToString(int[] array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
} else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append(array[i]);
}
sb.append(ARRAY_END);
return sb.toString();
}
/**
* Return a String representation of the contents of the specified array.
* <p>The String representation consists of a list of the array's elements,
* enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated
* by the characters <code>", "</code> (a comma followed by a space). Returns
* <code>"null"</code> if <code>array</code> is <code>null</code>.
*
* @param array the array to build a String representation for
* @return a String representation of <code>array</code>
*/
public static String nullSafeToString(long[] array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
} else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append(array[i]);
}
sb.append(ARRAY_END);
return sb.toString();
}
/**
* Return a String representation of the contents of the specified array.
* <p>The String representation consists of a list of the array's elements,
* enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated
* by the characters <code>", "</code> (a comma followed by a space). Returns
* <code>"null"</code> if <code>array</code> is <code>null</code>.
*
* @param array the array to build a String representation for
* @return a String representation of <code>array</code>
*/
public static String nullSafeToString(short[] array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
} else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append(array[i]);
}
sb.append(ARRAY_END);
return sb.toString();
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.lang;
import java.security.Provider;
import java.security.Security;
import java.util.concurrent.atomic.AtomicBoolean;
public class RuntimeEnvironment {
private static final String BC_PROVIDER_CLASS_NAME = "org.bouncycastle.jce.provider.BouncyCastleProvider";
private static final AtomicBoolean bcLoaded = new AtomicBoolean(false);
public static void enableBouncyCastleIfPossible() {
if (bcLoaded.get()) {
return;
}
try {
Class clazz = Classes.forName(BC_PROVIDER_CLASS_NAME);
//check to see if the user has already registered the BC provider:
Provider[] providers = Security.getProviders();
for(Provider provider : providers) {
if (clazz.isInstance(provider)) {
bcLoaded.set(true);
return;
}
}
//bc provider not enabled - add it:
Security.addProvider((Provider)Classes.newInstance(clazz));
bcLoaded.set(true);
} catch (UnknownClassException e) {
//not available
}
}
static {
enableBouncyCastleIfPossible();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,61 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.lang;
/**
* A <code>RuntimeException</code> equivalent of the JDK's
* <code>ClassNotFoundException</code>, to maintain a RuntimeException paradigm.
*
* @since 0.1
*/
public class UnknownClassException extends RuntimeException {
/**
* Creates a new UnknownClassException.
*/
public UnknownClassException() {
super();
}
/**
* Constructs a new UnknownClassException.
*
* @param message the reason for the exception
*/
public UnknownClassException(String message) {
super(message);
}
/**
* Constructs a new UnknownClassException.
*
* @param cause the underlying Throwable that caused this exception to be thrown.
*/
public UnknownClassException(Throwable cause) {
super(cause);
}
/**
* Constructs a new UnknownClassException.
*
* @param message the reason for the exception
* @param cause the underlying Throwable that caused this exception to be thrown.
*/
public UnknownClassException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,244 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken
import org.testng.annotations.Test
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.PrivateKey
import java.security.PublicKey
import java.security.SecureRandom
import static org.testng.Assert.*
class JWTsTest {
@Test
void testPlaintextJwtString() {
// Assert exact output per example at https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-6.1
// The base64url encoding of the example claims set in the spec shows that their original payload ends lines with
// carriage return + newline, so we have to include them in the test payload to assert our encoded output
// matches what is in the spec:
def payload = '{"iss":"joe",\r\n' +
' "exp":1300819380,\r\n' +
' "http://example.com/is_root":true}'
String val = JWTs.builder().setPayload(payload).compact();
def specOutput = 'eyJhbGciOiJub25lIn0.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.'
assertEquals val, specOutput
}
@Test
void testParsePlaintextToken() {
def claims = [iss: 'joe', exp: 1300819380, 'http://example.com/is_root':true]
String jwt = JWTs.builder().setClaims(claims).compact();
def token = JWTs.parser().parse(jwt);
assertEquals token.body, claims
}
@Test(expectedExceptions = IllegalArgumentException)
void testParseNull() {
JWTs.parser().parse(null)
}
@Test(expectedExceptions = IllegalArgumentException)
void testParseEmptyString() {
JWTs.parser().parse('')
}
@Test(expectedExceptions = IllegalArgumentException)
void testParseWhitespaceString() {
JWTs.parser().parse(' ')
}
@Test
void testParseWithNoPeriods() {
try {
JWTs.parser().parse('foo')
fail()
} catch (MalformedJwtException e) {
assertEquals e.message, "JWT strings must contain exactly 2 period characters. Found: 0"
}
}
@Test
void testParseWithOnePeriodOnly() {
try {
JWTs.parser().parse('.')
fail()
} catch (MalformedJwtException e) {
assertEquals e.message, "JWT strings must contain exactly 2 period characters. Found: 1"
}
}
@Test
void testParseWithTwoPeriodsOnly() {
try {
JWTs.parser().parse('..')
fail()
} catch (MalformedJwtException e) {
assertEquals e.message, "JWT string '..' is missing a body/payload."
}
}
@Test
void testParseWithHeaderOnly() {
try {
JWTs.parser().parse('foo..')
fail()
} catch (MalformedJwtException e) {
assertEquals e.message, "JWT string 'foo..' is missing a body/payload."
}
}
@Test
void testParseWithSignatureOnly() {
try {
JWTs.parser().parse('..bar')
fail()
} catch (MalformedJwtException e) {
assertEquals e.message, "JWT string '..bar' is missing a body/payload."
}
}
@Test
void testParseWithHeaderAndSignatureOnly() {
try {
JWTs.parser().parse('foo..bar')
fail()
} catch (MalformedJwtException e) {
assertEquals e.message, "JWT string 'foo..bar' is missing a body/payload."
}
}
@Test
void testHS256() {
testHmac(SignatureAlgorithm.HS256);
}
@Test
void testHS384() {
testHmac(SignatureAlgorithm.HS384);
}
@Test
void testHS512() {
testHmac(SignatureAlgorithm.HS512);
}
@Test
void testRS256() {
testRsa(SignatureAlgorithm.RS256);
}
@Test
void testRS384() {
testRsa(SignatureAlgorithm.RS384);
}
@Test
void testRS512() {
testRsa(SignatureAlgorithm.RS512);
}
@Test
void testPS256() {
testRsa(SignatureAlgorithm.PS256);
}
@Test
void testPS384() {
testRsa(SignatureAlgorithm.PS384);
}
@Test
void testPS512() {
testRsa(SignatureAlgorithm.PS512, 2048, false);
}
@Test
void testRSA256WithPrivateKeyValidation() {
testRsa(SignatureAlgorithm.RS256, 1024, true);
}
@Test
void testRSA384WithPrivateKeyValidation() {
testRsa(SignatureAlgorithm.RS384, 1024, true);
}
@Test
void testRSA512WithPrivateKeyValidation() {
testRsa(SignatureAlgorithm.RS512, 1024, true);
}
static void testRsa(SignatureAlgorithm alg) {
testRsa(alg, 1024, false);
}
static void testRsa(SignatureAlgorithm alg, int keySize, boolean verifyWithPrivateKey) {
KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA");
keyGenerator.initialize(keySize);
KeyPair kp = keyGenerator.genKeyPair();
PublicKey publicKey = kp.getPublic();
PrivateKey privateKey = kp.getPrivate();
def claims = [iss: 'joe', exp: 1300819380, 'http://example.com/is_root':true]
String jwt = JWTs.builder().setClaims(claims).signWith(alg, privateKey).compact();
def key = publicKey;
if (verifyWithPrivateKey) {
key = privateKey;
}
def token = JWTs.parser().setSigningKey(key).parse(jwt);
assertEquals token.header, [alg: alg.name()]
assertEquals token.body, claims
}
static void testHmac(SignatureAlgorithm alg) {
//create random signing key for testing:
Random random = new SecureRandom();
byte[] key = new byte[64];
random.nextBytes(key);
def claims = [iss: 'joe', exp: 1300819380, 'http://example.com/is_root':true]
String jwt = JWTs.builder().setClaims(claims).signWith(alg, key).compact();
def token = JWTs.parser().setSigningKey(key).parse(jwt)
assertEquals token.header, [alg: alg.name()]
assertEquals token.body, claims
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken
import org.testng.annotations.Test
import static org.testng.Assert.*
class SignatureAlgorithmTest {
@Test
void testNames() {
def algNames = ['HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512',
'ES256', 'ES384', 'ES512', 'PS256', 'PS384', 'PS512', 'NONE']
for( String name : algNames ) {
testName(name)
}
}
private static void testName(String name) {
def alg = SignatureAlgorithm.forName(name);
def namedAlg = name as SignatureAlgorithm //Groovy type coercion FTW!
assertTrue alg == namedAlg
assert alg.description != null && alg.description != ""
}
@Test(expectedExceptions = SignatureException)
void testUnrecognizedAlgorithmName() {
SignatureAlgorithm.forName('whatever')
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
import org.testng.annotations.Test
import javax.crypto.spec.SecretKeySpec
import static org.testng.Assert.*
class DefaultSignerFactoryTest {
private static final Random rng = new Random(); //doesn't need to be secure - we're just testing
@Test
void testCreateSignerWithNoneAlgorithm() {
byte[] keyBytes = new byte[32];
rng.nextBytes(keyBytes);
SecretKeySpec key = new SecretKeySpec(keyBytes, "foo");
def factory = new DefaultSignerFactory();
try {
factory.createSigner(SignatureAlgorithm.NONE, key);
fail();
} catch (IllegalArgumentException iae) {
assertEquals iae.message, "The 'NONE' algorithm cannot be used for signing."
}
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed 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 io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.SignatureException
import org.testng.annotations.Test
import static org.testng.Assert.*
import javax.crypto.Mac
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
class MacSignerTest {
private static final Random rng = new Random(); //doesn't need to be secure - we're just testing
@Test
void testNoSuchAlgorithmException() {
byte[] key = new byte[32];
byte[] data = new byte[32];
rng.nextBytes(key);
rng.nextBytes(data);
def s = new MacSigner(SignatureAlgorithm.HS256, key) {
@Override
protected Mac doGetMacInstance() throws NoSuchAlgorithmException, InvalidKeyException {
throw new NoSuchAlgorithmException("foo");
}
}
try {
s.sign(data);
fail();
} catch (SignatureException e) {
assertTrue e.cause instanceof NoSuchAlgorithmException
assertEquals e.cause.message, 'foo'
}
}
@Test
void testInvalidKeyException() {
byte[] key = new byte[32];
byte[] data = new byte[32];
rng.nextBytes(key);
rng.nextBytes(data);
def s = new MacSigner(SignatureAlgorithm.HS256, key) {
@Override
protected Mac doGetMacInstance() throws NoSuchAlgorithmException, InvalidKeyException {
throw new InvalidKeyException("foo");
}
}
try {
s.sign(data);
fail();
} catch (SignatureException e) {
assertTrue e.cause instanceof InvalidKeyException
assertEquals e.cause.message, 'foo'
}
}
}