io.vertx.up.secure.provider.JwtAuthProvider Maven / Gradle / Ivy
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