blob: e8ed870312f0a88308a8e1a2d58a0230b4dc139b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.geronimo.microprofile.impl.jwtauth.jwt;
import static java.util.Collections.emptyMap;
import java.io.ByteArrayInputStream;
import java.net.HttpURLConnection;
import java.util.Base64;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonReaderFactory;
import javax.json.JsonString;
import org.apache.geronimo.microprofile.impl.jwtauth.JwtException;
import org.apache.geronimo.microprofile.impl.jwtauth.config.GeronimoJwtAuthConfig;
import org.eclipse.microprofile.jwt.Claims;
import org.eclipse.microprofile.jwt.JsonWebToken;
@ApplicationScoped
public class JwtParser {
@Inject
private GeronimoJwtAuthConfig config;
@Inject
private KidMapper kidMapper;
@Inject
private DateValidator dateValidator;
@Inject
private SignatureValidator signatureValidator;
private JsonReaderFactory readerFactory;
private String defaultKid;
private String defaultAlg;
private String defaultTyp;
private boolean validateTyp;
@PostConstruct
private void init() {
readerFactory = Json.createReaderFactory(emptyMap());
defaultKid = config.read("jwt.header.kid.default", null);
defaultAlg = config.read("jwt.header.alg.default", "RS256");
defaultTyp = config.read("jwt.header.typ.default", "JWT");
validateTyp = Boolean.parseBoolean(config.read("jwt.header.typ.validate", "true"));
}
public JsonWebToken parse(final String jwt) {
final int firstDot = jwt.indexOf('.');
if (firstDot < 0) {
throw new JwtException("JWT is not valid", HttpURLConnection.HTTP_BAD_REQUEST);
}
final int secondDot = jwt.indexOf('.', firstDot + 1);
if (secondDot < 0 || jwt.indexOf('.', secondDot + 1) > 0 || jwt.length() <= secondDot) {
throw new JwtException("JWT is not valid", HttpURLConnection.HTTP_BAD_REQUEST);
}
final String rawHeader = jwt.substring(0, firstDot);
final JsonObject header = loadJson(rawHeader);
if (validateTyp && !getAttribute(header, "typ", defaultTyp).equalsIgnoreCase("jwt")) {
throw new JwtException("Invalid typ", HttpURLConnection.HTTP_UNAUTHORIZED);
}
final JsonObject payload = loadJson(jwt.substring(firstDot + 1, secondDot));
dateValidator.checkInterval(payload);
final String alg = getAttribute(header, "alg", defaultAlg);
final String kid = getAttribute(header, "kid", defaultKid);
if (kidMapper.loadIssuers(kid).noneMatch(it -> it.equals(payload.getString(Claims.iss.name())))) {
throw new JwtException("Invalid issuer", HttpURLConnection.HTTP_UNAUTHORIZED);
}
signatureValidator.verifySignature(alg, kidMapper.loadKey(kid), jwt.substring(0, secondDot), jwt.substring(secondDot + 1));
return new GeronimoJsonWebToken(jwt, payload);
}
private String getAttribute(final JsonObject payload, final String key, final String def) {
final JsonString json = payload.getJsonString(key);
final String value = json != null ? json.getString() : def;
if (value == null) {
throw new JwtException("No " + key + " in JWT", HttpURLConnection.HTTP_UNAUTHORIZED);
}
return value;
}
private JsonObject loadJson(final String src) {
return readerFactory.createReader(new ByteArrayInputStream(Base64.getUrlDecoder().decode(src))).readObject();
}
}