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

io.vertx.ext.web.handler.impl.JWTAuthHandlerImpl Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014 Red Hat, Inc.
 *
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License v1.0
 *  and Apache License v2.0 which accompanies this distribution.
 *
 *  The Eclipse Public License is available at
 *  http://www.eclipse.org/legal/epl-v10.html
 *
 *  The Apache License v2.0 is available at
 *  http://www.opensource.org/licenses/apache2.0.php
 *
 *  You may elect to redistribute this code under either of these licenses.
 */

package io.vertx.ext.web.handler.impl;

import io.vertx.core.Future;
import io.vertx.core.VertxException;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.User;
import io.vertx.ext.auth.audit.Marker;
import io.vertx.ext.auth.audit.SecurityAudit;
import io.vertx.ext.auth.authentication.TokenCredentials;
import io.vertx.ext.auth.jwt.JWTAuth;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.HttpException;
import io.vertx.ext.web.handler.JWTAuthHandler;
import io.vertx.ext.web.impl.RoutingContextInternal;
import io.vertx.ext.web.internal.handler.ScopedAuthentication;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author Paulo Lopes
 */
public class JWTAuthHandlerImpl extends HTTPAuthorizationHandler implements JWTAuthHandler, ScopedAuthentication {

  private final List scopes;
  private String delimiter;

  public JWTAuthHandlerImpl(JWTAuth authProvider, String realm) {
    super(authProvider, Type.BEARER, realm);
    scopes = Collections.emptyList();
    this.delimiter = " ";
  }

  private JWTAuthHandlerImpl(JWTAuthHandlerImpl base, List scopes, String delimiter) {
    super(base.authProvider, Type.BEARER, base.realm);
    Objects.requireNonNull(scopes, "scopes cannot be null");
    this.scopes = scopes;
    Objects.requireNonNull(delimiter, "delimiter cannot be null");
    this.delimiter = delimiter;
  }

  @Override
  public Future authenticate(RoutingContext context) {

    return parseAuthorization(context)
      .compose(token -> {
        int segments = 0;
        for (int i = 0; i < token.length(); i++) {
          char c = token.charAt(i);
          if (c == '.') {
            if (++segments == 3) {
              return Future.failedFuture(new HttpException(400, "Too many segments in token"));
            }
            continue;
          }
          if (Character.isLetterOrDigit(c) || c == '-' || c == '_') {
            continue;
          }
          // invalid character
          return Future.failedFuture(new HttpException(400, "Invalid character in token: " + (int) c));
        }

        final TokenCredentials credentials = new TokenCredentials(token);
        final SecurityAudit audit = ((RoutingContextInternal) context).securityAudit();
        audit.credentials(credentials);

        return
          authProvider
            .authenticate(new TokenCredentials(token))
            .andThen(op -> audit.audit(Marker.AUTHENTICATION, op.succeeded()))
            .recover(err -> Future.failedFuture(new HttpException(401, err)));
      });
  }

  @Override
  public JWTAuthHandler withScope(String scope) {
    Objects.requireNonNull(scope, "scope cannot be null");
    List updatedScopes = new ArrayList<>(this.scopes);
    updatedScopes.add(scope);
    return new JWTAuthHandlerImpl(this, updatedScopes, delimiter);
  }

  @Override
  public JWTAuthHandler withScopes(List scopes) {
    Objects.requireNonNull(scopes, "scopes cannot be null");
    return new JWTAuthHandlerImpl(this, scopes, delimiter);
  }

  @Override
  public JWTAuthHandler scopeDelimiter(String delimiter) {
    Objects.requireNonNull(delimiter, "delimiter cannot be null");
    this.delimiter = delimiter;
    return this;
  }

  /**
   * The default behavior for post-authentication
   */
  @Override
  public void postAuthentication(RoutingContext ctx) {
    final User user = ctx.user().get();
    if (user == null) {
      // bad state
      ctx.fail(403, new VertxException("no user in the context", true));
      return;
    }
    // the user is authenticated, however the user may not have all the required scopes
    final List scopes = getScopesOrSearchMetadata(this.scopes, ctx);

    if (scopes.size() > 0) {
      final JsonObject jwt = user.get("accessToken");
      if (jwt == null) {
        ctx.fail(403, new VertxException("Invalid JWT: null", true));
        return;
      }

      if (jwt.getValue("scope") == null) {
        ctx.fail(403, new VertxException("Invalid JWT: scope claim is required", true));
        return;
      }

      List target;
      if (jwt.getValue("scope") instanceof String) {
        target =
          Stream.of(jwt.getString("scope")
              .split(delimiter))
            .collect(Collectors.toList());
      } else {
        target = jwt.getJsonArray("scope").getList();
      }

      if (target != null) {
        for (String scope : scopes) {
          if (!target.contains(scope)) {
            ctx.fail(403, new VertxException("JWT scopes != handler scopes", true));
            return;
          }
        }
      }
    }
    ctx.next();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy