All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.security.jwt.JwtHelper Maven / Gradle / Ivy

Go to download

Spring Security JWT is a small utility library for encoding and decoding JSON Web Tokens. It belongs to the family of Spring Security crypto libraries that handle encoding and decoding text as a general, useful thing to be able to do.

There is a newer version: 1.1.1.RELEASE
Show newest version
/*
 * Copyright 2006-2011 the original author or authors.
 *
 * 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 org.springframework.security.jwt;

import static org.springframework.security.jwt.JwtAlgorithms.sigAlg;
import static org.springframework.security.jwt.codec.Codecs.b64UrlDecode;
import static org.springframework.security.jwt.codec.Codecs.b64UrlEncode;
import static org.springframework.security.jwt.codec.Codecs.concat;
import static org.springframework.security.jwt.codec.Codecs.utf8Decode;
import static org.springframework.security.jwt.codec.Codecs.utf8Encode;

import java.nio.CharBuffer;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

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(".");

	/**
	 * Creates a token from an encoded token string.
	 *
	 * @param token the (non-null) encoded token (three Base-64 encoded strings separated
	 * by "." characters)
	 */
	public static Jwt decode(String token) {
		int firstPeriod = token.indexOf('.');
		int lastPeriod = token.lastIndexOf('.');

		if (firstPeriod <= 0 || lastPeriod <= firstPeriod) {
			throw new IllegalArgumentException("JWT must have 3 tokens");
		}
		CharBuffer buffer = CharBuffer.wrap(token, 0, firstPeriod);
		// TODO: Use a Reader which supports CharBuffer
		JwtHeader header = JwtHeaderHelper.create(buffer.toString());

		buffer.limit(lastPeriod).position(firstPeriod + 1);
		byte[] claims = b64UrlDecode(buffer);
		boolean emptyCrypto = lastPeriod == token.length() - 1;

		byte[] crypto;

		if (emptyCrypto) {
			if (!"none".equals(header.parameters.alg)) {
				throw new IllegalArgumentException(
						"Signed or encrypted token must have non-empty crypto segment");
			}
			crypto = new byte[0];
		}
		else {
			buffer.limit(token.length()).position(lastPeriod + 1);
			crypto = b64UrlDecode(buffer);
		}
		return new JwtImpl(header, claims, crypto);
	}

	public static Jwt decodeAndVerify(String token, SignatureVerifier verifier) {
		Jwt jwt = decode(token);
		jwt.verifySignature(verifier);

		return jwt;
	}
	
	public static Map headers(String token) {
		JwtImpl jwt = (JwtImpl) decode(token);
		Map map = new LinkedHashMap(jwt.header.parameters.map);
		map.put("alg", jwt.header.parameters.alg);
		if (jwt.header.parameters.typ!=null) {
			map.put("typ", jwt.header.parameters.typ);
		}
		return map;
	}

	public static Jwt encode(CharSequence content, Signer signer) {
		return encode(content, signer, Collections.emptyMap());
	}

	public static Jwt encode(CharSequence content, Signer signer,
			Map headers) {
		JwtHeader header = JwtHeaderHelper.create(signer, headers);
		byte[] claims = utf8Encode(content);
		byte[] crypto = signer
				.sign(concat(b64UrlEncode(header.bytes()), PERIOD, b64UrlEncode(claims)));
		return new JwtImpl(header, claims, crypto);
	}
}

/**
 * Helper object for JwtHeader.
 *
 * Handles the JSON parsing and serialization.
 */
class JwtHeaderHelper {

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

	static JwtHeader create(Signer signer, Map params) {
		Map map = new LinkedHashMap(params);
		map.put("alg", sigAlg(signer.algorithm()));
		HeaderParameters p = new HeaderParameters(map);
		return new JwtHeader(serializeParams(p), p);
	}

	static HeaderParameters parseParams(byte[] header) {
		Map map = parseMap(utf8Decode(header));
		return new HeaderParameters(map);
	}

	private static Map parseMap(String json) {
		if (json != null) {
			json = json.trim();
			if (json.startsWith("{")) {
				return parseMapInternal(json);
			}
			else if (json.equals("")) {
				return new LinkedHashMap();
			}
		}
		throw new IllegalArgumentException("Invalid JSON (null)");
	}

	private static Map parseMapInternal(String json) {
		Map map = new LinkedHashMap();
		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 (map.containsKey(key)) {
				throw new IllegalArgumentException("Duplicate '" + key + "' field");
			}
			map.put(key, value);
		}
		return map;
	}

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

	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);
		}
		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.typ != null) {
			appendField(builder, "typ", params.typ);
		}
		for (Entry entry : params.map.entrySet()) {
			appendField(builder, entry.getKey(), entry.getValue());
		}
		builder.append("}");
		return utf8Encode(builder.toString());

	}

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

/**
 * Header part of JWT
 *
 */
class JwtHeader implements BinaryFormat {
	private final byte[] bytes;

	final HeaderParameters parameters;

	/**
	 * @param bytes the decoded header
	 * @param parameters the parameter values contained in the header
	 */
	JwtHeader(byte[] bytes, HeaderParameters parameters) {
		this.bytes = bytes;
		this.parameters = parameters;
	}

	@Override
	public byte[] bytes() {
		return bytes;
	}

	@Override
	public String toString() {
		return utf8Decode(bytes);
	}
}

class HeaderParameters {
	final String alg;

	final Map map;

	final String typ = "JWT";

	HeaderParameters(String alg) {
		this(new LinkedHashMap(Collections.singletonMap("alg", alg)));
	}

	HeaderParameters(Map map) {
		String alg = map.get("alg"), typ = map.get("typ");
		if (typ != null && !"JWT".equalsIgnoreCase(typ)) {
			throw new IllegalArgumentException("typ is not \"JWT\"");
		}
		map.remove("alg");
		map.remove("typ");
		this.map = map;
		if (alg == null) {
			throw new IllegalArgumentException("alg is required");
		}
		this.alg = alg;
	}

}

class JwtImpl implements Jwt {
	final JwtHeader header;

	private final byte[] content;

	private final byte[] crypto;

	private String claims;

	/**
	 * @param header the header, containing the JWS/JWE algorithm information.
	 * @param content the base64-decoded "claims" segment (may be encrypted, depending on
	 * header information).
	 * @param crypto the base64-decoded "crypto" segment.
	 */
	JwtImpl(JwtHeader header, byte[] content, byte[] crypto) {
		this.header = header;
		this.content = content;
		this.crypto = crypto;
		claims = utf8Decode(content);
	}

	/**
	 * Validates a signature contained in the 'crypto' segment.
	 *
	 * @param verifier the signature verifier
	 */
	@Override
	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
	 */
	@Override
	public byte[] bytes() {
		return concat(b64UrlEncode(header.bytes()), JwtHelper.PERIOD,
				b64UrlEncode(content), JwtHelper.PERIOD, b64UrlEncode(crypto));
	}

	@Override
	public String getClaims() {
		return utf8Decode(content);
	}

	@Override
	public String getEncoded() {
		return utf8Decode(bytes());
	}
	
	public JwtHeader header() {
		return this.header;
	}

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




© 2015 - 2024 Weber Informatics LLC | Privacy Policy