io.flowly.auth.ExtJwtAuthProvider.java Source code

Java tutorial

Introduction

Here is the source code for io.flowly.auth.ExtJwtAuthProvider.java

Source

/*
 * Copyright (c) 2015 The original author or authors.
 *
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the Apache License v2.0 
 *  which accompanies this distribution.
 *
 *  The Apache License v2.0 is available at
 *  http://opensource.org/licenses/Apache-2.0
 *
 *  You may elect to redistribute this code under this license.
 */

package io.flowly.auth;

import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.impl.VertxInternal;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.User;
import io.vertx.ext.auth.jwt.JWTAuth;
import io.vertx.ext.auth.jwt.JWTOptions;
import io.vertx.ext.auth.jwt.impl.JWT;
import io.vertx.ext.auth.jwt.impl.JWTAuthProviderImpl;
import io.vertx.ext.auth.jwt.impl.JWTUser;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.Collections;

/**
 * Extended JWT auth provider that adds effective permissions to the autheticated user token.
 * Minor modifications to io.vertx.ext.auth.jwt.impl.JWTAuthProviderImpl.
 *
 * @author <a>Paulo Lopes</a>
 * @author <a>Uday Tatiraju</a>
 */
public class ExtJwtAuthProvider implements JWTAuth {
    private static final JsonObject EMPTY_OBJECT = new JsonObject();
    private static final JsonArray EMPTY_ARRAY = new JsonArray();

    private final JWT jwt;

    private final String permissionsClaimKey;

    public ExtJwtAuthProvider(Vertx vertx, JsonObject config) {
        this.permissionsClaimKey = config.getString("permissionsClaimKey", "permissions");

        final JsonObject keyStore = config.getJsonObject("keyStore");

        try {
            if (keyStore != null) {
                KeyStore ks = KeyStore.getInstance(keyStore.getString("type", "jceks"));

                VertxInternal vertxInternal = (VertxInternal) vertx;

                try (InputStream in = new FileInputStream(vertxInternal.resolveFile(keyStore.getString("path")))) {
                    ks.load(in, keyStore.getString(io.flowly.core.security.User.PASSWORD).toCharArray());
                }

                this.jwt = new JWT(ks, keyStore.getString(io.flowly.core.security.User.PASSWORD).toCharArray());
            } else {
                this.jwt = new JWT(null, null);
            }

        } catch (KeyStoreException | IOException | CertificateException | NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void authenticate(JsonObject authInfo, Handler<AsyncResult<User>> resultHandler) {
        try {
            final JsonObject payload = jwt.decode(authInfo.getString("jwt"));

            final JsonObject options = authInfo.getJsonObject("options", EMPTY_OBJECT);

            // All dates in JWT are of type NumericDate
            // a NumericDate is: numeric value representing the number of seconds from 1970-01-01T00:00:00Z UTC until
            // the specified UTC date/time, ignoring leap seconds
            final long now = System.currentTimeMillis() / 1000;

            if (payload.containsKey("exp") && !options.getBoolean("ignoreExpiration", false)) {
                if (now >= payload.getLong("exp")) {
                    resultHandler.handle(Future.failedFuture("Expired JWT token: exp <= now"));
                    return;
                }
            }

            if (payload.containsKey("iat")) {
                Long iat = payload.getLong("iat");
                // issue at must be in the past
                if (iat > now) {
                    resultHandler.handle(Future.failedFuture("Invalid JWT token: iat > now"));
                    return;
                }
            }

            if (payload.containsKey("nbf")) {
                Long nbf = payload.getLong("nbf");
                // not before must be after now
                if (nbf > now) {
                    resultHandler.handle(Future.failedFuture("Invalid JWT token: nbf > now"));
                    return;
                }
            }

            if (options.containsKey("audience")) {
                JsonArray audiences = options.getJsonArray("audience", EMPTY_ARRAY);
                JsonArray target = payload.getJsonArray("aud", EMPTY_ARRAY);

                if (Collections.disjoint(audiences.getList(), target.getList())) {
                    resultHandler
                            .handle(Future.failedFuture("Invalid JWT audience. expected: " + audiences.encode()));
                    return;
                }
            }

            if (options.containsKey("issuer")) {
                if (!options.getString("issuer").equals(payload.getString("iss"))) {
                    resultHandler.handle(Future.failedFuture("Invalid JWT issuer"));
                    return;
                }
            }

            resultHandler.handle(Future.succeededFuture(new ExtJwtUser(payload, permissionsClaimKey)));

        } catch (RuntimeException e) {
            resultHandler.handle(Future.failedFuture(e));
        }
    }

    @Override
    public String generateToken(JsonObject claims, final JWTOptions options) {
        final JsonObject jsonOptions = options.toJSON();

        // we do some "enhancement" of the claims to support roles and permissions
        if (jsonOptions.containsKey("permissions") && !claims.containsKey(permissionsClaimKey)) {
            claims.put(permissionsClaimKey, jsonOptions.getJsonArray("permissions"));
        }

        return jwt.sign(claims, options.toJSON());
    }
}