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

io.vertx.up.secure.provider.JwtAuthProvider Maven / Gradle / Ivy

There is a newer version: 0.9.0
Show newest version
package io.vertx.up.secure.provider;

import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.json.Json;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.shareddata.AsyncMap;
import io.vertx.ext.auth.User;
import io.vertx.ext.auth.jwt.JWTAuthOptions;
import io.vertx.ext.jwt.JWT;
import io.vertx.ext.jwt.JWTOptions;
import io.vertx.up.exception.web.*;
import io.vertx.up.fn.Fn;
import io.vertx.up.log.Annal;
import io.vertx.up.secure.Security;
import io.vertx.up.unity.Ux;

import java.util.Collections;
import java.util.function.Supplier;

public class JwtAuthProvider implements JwtAuth {
    private static final JsonArray EMPTY_ARRAY = new JsonArray();
    private static final String AUTH_POOL = "JWT_AUTH_TOKEN_POOL";
    private static final Annal LOGGER = Annal.get(JwtAuthProvider.class);
    private final JWT jwt;
    private final String permissionsClaimKey;
    private final JWTOptions jwtOptions;
    private final transient JwtSecurer securer = JwtSecurer.create();

    private transient AsyncMap sessionTokens;

    JwtAuthProvider(final Vertx vertx, final JWTAuthOptions config) {
        permissionsClaimKey = config.getPermissionsClaimKey();
        // Set this key to securer
        securer.setPermissionsClaimKey(permissionsClaimKey);
        jwtOptions = config.getJWTOptions();
        // File reading here.
        jwt = Ux.Jwt.create(config, vertx.fileSystem()::readFileBlocking);
        vertx.sharedData().getAsyncMap(AUTH_POOL, res -> {
            if (res.succeeded()) {
                LOGGER.debug(Info.MAP_INITED, AUTH_POOL);
                sessionTokens = res.result();
            }
        });
    }

    @Override
    public JwtAuth bind(final Supplier supplier) {
        final Security security = supplier.get();
        /*
         * Zero Security Framework needed, could not break this rule.
         */
        Fn.outWeb(null == security, _500SecurityNotImplementException.class, getClass());
        securer.setSecurity(security);
        return this;
    }

    @Override
    public void authenticate(final JsonObject authInfo, final Handler> handler) {
        LOGGER.info("[ ZERO ] Auth Information: {0}", authInfo.encode());
        final String token = authInfo.getString("jwt");
        /*
         * Extract token from sessionTokens here
         */
        if (null == sessionTokens) {
            /*
             * Session tokens is null, it means zero system will disabled token cache future here.
             * It will go through common authenticate workflow here.
             * 1) 401 Validation
             * 2) 403 Validation ( If So )
             */
            LOGGER.debug(Info.FLOW_NULL, token);
            prerequisite(token)
                    /* 401 */
                    .compose(nil -> securer.authenticate(authInfo))
                    /* Mount Handler */
                    .setHandler(authorized(token, handler));
        } else {
            /*
             * Get token from sessionTokens
             */
            sessionTokens.get(token, res -> {
                if (null != res && null != res.result() && res.result()) {
                    /* Token verified from cache */
                    LOGGER.info(Info.MAP_HIT, token, res.result());
                    /*
                     * Also this situation, the prerequisite step could be skipped because it has
                     * been calculated before, the token is valid and we could process this token
                     * go to 403 workflow directly.
                     * 401 Validation Skip
                     * 403 Validation ( If So )
                     */
                    securer.authorize(authInfo)
                            /* Mount Handler */
                            .setHandler(authorized(token, handler));
                } else {
                    LOGGER.debug(Info.MAP_MISSING, token);
                    /*
                     * Repeat do 401 validation & 403 validation
                     * 1) 401 Validation
                     * 2) 403 Validation ( If So )
                     */
                    prerequisite(token)
                            /* 401 */
                            .compose(nil -> securer.authenticate(authInfo))
                            /* Mount Handler */
                            .setHandler(authorized(token, handler));
                }
            });
        }
    }

    private Handler> authorized(final String token, final Handler> handler) {
        return user -> {
            if (user.succeeded()) {
                /* Store token into async map to refresh cache and then return */
                sessionTokens.put(token, Boolean.TRUE, result -> {
                    LOGGER.debug(Info.MAP_PUT, token, Boolean.TRUE);
                    handler.handle(Future.succeededFuture(user.result()));
                });
            } else {
                /* Capture the result from internal calling */
                final Throwable error = user.cause();
                handler.handle(Future.failedFuture(error));
            }
        };
    }

    /*
     * Prerequisite for JWT token here based on jwtOptions & jwt
     * This function is provided by zero system and it will verify jwt token specification only
     * It does not contain any business code logical here.
     */
    @SuppressWarnings("all")
    private Future prerequisite(final String token) {
        try {
            final JsonObject payload = this.jwt.decode(token);
            if (this.jwt.isExpired(payload, this.jwtOptions)) {
                return Future.failedFuture(new _401JwtExpiredException(this.getClass(), payload));
            }
            if (this.jwtOptions.getAudience() != null) {
                final JsonArray target;
                if (payload.getValue("aud") instanceof String) {
                    target = (new JsonArray()).add(payload.getValue("aud", ""));
                } else {
                    target = payload.getJsonArray("aud", EMPTY_ARRAY);
                }

                if (Collections.disjoint(this.jwtOptions.getAudience(), target.getList())) {
                    return Future.failedFuture(new _401JwtAudientException(this.getClass(), Json.encode(this.jwtOptions.getAudience())));
                }
            }

            if (this.jwtOptions.getIssuer() != null && !this.jwtOptions.getIssuer().equals(payload.getString("iss"))) {
                return Future.failedFuture(new _401JwtIssuerException(this.getClass(), payload.getString("iss")));
            }
            return Future.succeededFuture(token);
        } catch (final RuntimeException ex) {
            return Future.failedFuture(new _500JwtRuntimeException(this.getClass(), ex));
        }
    }

    @Override
    public String generateToken(final JsonObject claims, final JWTOptions options) {
        final JsonObject _claims = claims.copy();
        if (options.getPermissions() != null && !_claims.containsKey(permissionsClaimKey)) {
            _claims.put(permissionsClaimKey, new JsonArray(options.getPermissions()));
        }
        return jwt.sign(_claims, options);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy