Skip to content

Commit

Permalink
Parse JSON manually (avoid dependency on Jackson)
Browse files Browse the repository at this point in the history
The only JSON we need to parse is the header and that is very simple,
so it's probably nicer for users to not depend on any parser.

Fixes spring-atticgh-400
  • Loading branch information
Dave Syer committed Feb 13, 2015
1 parent 8a68cf2 commit ec907a1
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 96 deletions.
19 changes: 7 additions & 12 deletions spring-security-jwt/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.3.BUILD-SNAPSHOT</version>
<version>1.0.3.RELEASE</version>
<packaging>jar</packaging>
<name>Spring Security JWT Library</name>

Expand All @@ -26,12 +26,6 @@
</licenses>

<dependencies>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.13</version>
</dependency>

<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
Expand Down Expand Up @@ -74,7 +68,8 @@
<includes>
<include>**/*Tests.java</include>
</includes>
<!-- <systemPropertyVariables> <jruby.home>${jruby.home}</jruby.home> </systemPropertyVariables> -->
<!-- <systemPropertyVariables> <jruby.home>${jruby.home}</jruby.home>
</systemPropertyVariables> -->
</configuration>

</plugin>
Expand Down Expand Up @@ -134,15 +129,15 @@
</site>

<repository>
<id>springframework-release</id>
<id>repo.spring.io</id>
<name>Spring Release Repository</name>
<url>s3://maven.springframework.org/release</url>
<url>https://repo.spring.io/libs-release-local</url>
</repository>

<snapshotRepository>
<id>springframework-snapshot</id>
<id>repo.spring.io</id>
<name>Spring Snapshot Repository</name>
<url>s3://maven.springframework.org/snapshot</url>
<url>https://repo.spring.io/libs-snapshot-local</url>
</snapshotRepository>

</distributionManagement>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,16 @@
import static org.springframework.security.jwt.codec.Codecs.utf8Decode;
import static org.springframework.security.jwt.codec.Codecs.utf8Encode;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.CharBuffer;
import java.util.LinkedHashMap;
import java.util.Map;

import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;
import org.springframework.security.jwt.crypto.sign.SignatureVerifier;
import org.springframework.security.jwt.crypto.sign.Signer;

/**
* @author Luke Taylor
* @author Dave Syer
*/
public class JwtHelper {
static byte[] PERIOD = utf8Encode(".");
Expand All @@ -45,7 +42,7 @@ public static Jwt decode(String token) {
int firstPeriod = token.indexOf('.');
int lastPeriod = token.lastIndexOf('.');

if (firstPeriod <=0 || lastPeriod <= firstPeriod) {
if (firstPeriod <= 0 || lastPeriod <= firstPeriod) {
throw new IllegalArgumentException("JWT must have 3 tokens");
}
CharBuffer buffer = CharBuffer.wrap(token, 0, firstPeriod);
Expand All @@ -63,7 +60,8 @@ public static Jwt decode(String token) {
throw new IllegalArgumentException("Signed or encrypted token must have non-empty crypto segment");
}
crypto = new byte[0];
} else {
}
else {
buffer.limit(token.length()).position(lastPeriod + 1);
crypto = b64UrlDecode(buffer);
}
Expand Down Expand Up @@ -91,14 +89,12 @@ public static Jwt encode(CharSequence content, Signer signer) {
* Handles the JSON parsing and serialization.
*/
class JwtHeaderHelper {
private static final JsonFactory f = new JsonFactory();

static JwtHeader create(String header) {
byte[] bytes = b64UrlDecode(header);
return new JwtHeader(bytes, parseParams(bytes));
}


static JwtHeader create(Signer signer) {
HeaderParameters p = new HeaderParameters(sigAlg(signer.algorithm()), null, null);
return new JwtHeader(serializeParams(p), p);
Expand All @@ -110,84 +106,81 @@ static JwtHeader create(String alg, String enc, byte[] iv) {
}

static HeaderParameters parseParams(byte[] header) {
JsonParser jp = null;
try {
jp = f.createJsonParser(header);
String alg = null, enc = null, iv = null;
jp.nextToken();
while (jp.nextToken() != JsonToken.END_OBJECT) {
String fieldname = jp.getCurrentName();
jp.nextToken();
if (!JsonToken.VALUE_STRING.equals(jp.getCurrentToken())) {
throw new IllegalArgumentException("Header fields must be strings");
}
String value = jp.getText();
if ("alg".equals(fieldname)) {
if (alg != null) {
throw new IllegalArgumentException("Duplicate 'alg' field");
}
alg = value;
} else if ("enc".equals(fieldname)) {
if (enc != null) {
throw new IllegalArgumentException("Duplicate 'enc' field");
}
enc = value;
} if ("iv".equals(fieldname)) {
if (iv != null) {
throw new IllegalArgumentException("Duplicate 'iv' field");
}
iv = jp.nextToken().asString();
} else if ("typ".equals(fieldname)) {
if (!"JWT".equalsIgnoreCase(value)) {
throw new IllegalArgumentException("typ is not \"JWT\"");
}
}
}
Map<String, String> map = parseMap(utf8Decode(header));
String alg = map.get("alg"), enc = map.get("enc"), iv = map.get("iv"), typ = map.get("typ");
if (typ != null && !"JWT".equalsIgnoreCase(typ)) {
throw new IllegalArgumentException("typ is not \"JWT\"");
}
return new HeaderParameters(alg, enc, iv);
}

return new HeaderParameters(alg, enc, iv);
} catch (IOException io) {
throw new RuntimeException(io);
} finally {
if (jp != null) {
try {
jp.close();
} catch (IOException ignore) {
}
private static Map<String, String> parseMap(String json) {
if (json != null) {
json = json.trim();
if (json.startsWith("{")) {
return parseMapInternal(json);
}
else if (json.equals("")) {
return new LinkedHashMap<String, String>();
}
}
throw new IllegalArgumentException("Invalid JSON (null)");
}

private static byte[] serializeParams(HeaderParameters params) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
JsonGenerator g = null;

try {
g = f.createJsonGenerator(baos);
g.writeStartObject();
g.writeStringField("alg", params.alg);
if (params.enc != null) {
g.writeStringField("enc", params.enc);
private static Map<String, String> parseMapInternal(String json) {
Map<String, String> map = new LinkedHashMap<String, String>();
json = trimLeadingCharacter(trimTrailingCharacter(json, '}'), '{');
for (String pair : json.split(",")) {
String[] values = pair.split(":");
String key = strip(values[0], '"');
String value = null;
if (values.length > 0) {
value = strip(values[1], '"');
}
if (params.iv != null) {
g.writeStringField("iv", params.iv);
if (map.containsKey(key)) {
throw new IllegalArgumentException("Duplicate '" + key + "' field");
}
g.writeEndObject();
g.flush();
map.put(key, value);
}
return map;
}

private static String strip(String string, char c) {
return trimLeadingCharacter(trimTrailingCharacter(string.trim(), c), c);
}

return baos.toByteArray();
private static String trimTrailingCharacter(String string, char c) {
if (string.length() >= 0 && string.charAt(string.length() - 1) == c) {
return string.substring(0, string.length() - 1);
}
catch (IOException io) {
throw new RuntimeException(io);
} finally {
if (g != null) {
try {
g.close();
} catch (IOException ignore) {
}
}
return string;
}

private static String trimLeadingCharacter(String string, char c) {
if (string.length() >= 0 && string.charAt(0) == c) {
return string.substring(1);
}
return string;
}

private static byte[] serializeParams(HeaderParameters params) {
StringBuilder builder = new StringBuilder("{");

appendField(builder, "alg", params.alg);
if (params.enc != null) {
appendField(builder, "enc", params.enc);
}
if (params.iv != null) {
appendField(builder, "iv", params.iv);
}
builder.append("}");
return utf8Encode(builder.toString());

}

private static void appendField(StringBuilder builder, String name, String value) {
builder.append("\"").append(name).append("\":\"").append(value).append("\"");
}
}

/**
Expand All @@ -196,6 +189,7 @@ private static byte[] serializeParams(HeaderParameters params) {
*/
class JwtHeader implements BinaryFormat {
private final byte[] bytes;

final HeaderParameters parameters;

/**
Expand All @@ -219,7 +213,9 @@ public String toString() {

class HeaderParameters {
final String alg;

final String enc;

final String iv;

HeaderParameters(String alg) {
Expand All @@ -239,8 +235,11 @@ class HeaderParameters {

class JwtImpl implements Jwt {
private final JwtHeader header;

private final byte[] content;

private final byte[] crypto;

private String claims;

/**
Expand All @@ -262,19 +261,20 @@ class JwtImpl implements Jwt {
*/
public void verifySignature(SignatureVerifier verifier) {
verifier.verify(signingInput(), crypto);
}
}

private byte[] signingInput() {
return concat(b64UrlEncode(header.bytes()), JwtHelper.PERIOD, b64UrlEncode(content));
}

/**
* Allows retrieval of the full token.
*
* @return the encoded header, claims and crypto segments concatenated with "." characters
*/
/**
* Allows retrieval of the full token.
*
* @return the encoded header, claims and crypto segments concatenated with "." characters
*/
public byte[] bytes() {
return concat(b64UrlEncode(header.bytes()), JwtHelper.PERIOD, b64UrlEncode(content), JwtHelper.PERIOD, b64UrlEncode(crypto));
return concat(b64UrlEncode(header.bytes()), JwtHelper.PERIOD, b64UrlEncode(content), JwtHelper.PERIOD,
b64UrlEncode(crypto));
}

public String getClaims() {
Expand All @@ -287,6 +287,6 @@ public String getEncoded() {

@Override
public String toString() {
return header + " " + claims + " ["+ crypto.length + " crypto bytes]";
return header + " " + claims + " [" + crypto.length + " crypto bytes]";
}
}

0 comments on commit ec907a1

Please sign in to comment.