public Filter getFilter()

in extensions-core/druid-kerberos/src/main/java/org/apache/druid/security/kerberos/KerberosAuthenticator.java [122:368]


  public Filter getFilter()
  {
    return new AuthenticationFilter()
    {
      private Signer mySigner;

      @Override
      public void init(FilterConfig filterConfig) throws ServletException
      {
        ClassLoader prevLoader = Thread.currentThread().getContextClassLoader();
        try {
          // AuthenticationHandler is created during Authenticationfilter.init using reflection with thread context class loader.
          // In case of druid since the class is actually loaded as an extension and filter init is done in main thread.
          // We need to set the classloader explicitly to extension class loader.
          Thread.currentThread().setContextClassLoader(AuthenticationFilter.class.getClassLoader());
          super.init(filterConfig);
          String configPrefix = filterConfig.getInitParameter(CONFIG_PREFIX);
          configPrefix = (configPrefix != null) ? configPrefix + "." : "";
          Properties config = getConfiguration(configPrefix, filterConfig);
          String signatureSecret = config.getProperty(configPrefix + SIGNATURE_SECRET);
          if (signatureSecret == null) {
            signatureSecret = Long.toString(ThreadLocalRandom.current().nextLong());
            log.warn("'signature.secret' configuration not set, using a random value as secret");
          }
          final byte[] secretBytes = StringUtils.toUtf8(signatureSecret);
          SignerSecretProvider signerSecretProvider = new SignerSecretProvider()
          {
            @Override
            public void init(Properties config, ServletContext servletContext, long tokenValidity)
            {

            }

            @Override
            public byte[] getCurrentSecret()
            {
              return secretBytes;
            }

            @Override
            public byte[][] getAllSecrets()
            {
              return new byte[][]{secretBytes};
            }
          };
          mySigner = new Signer(signerSecretProvider);
        }
        finally {
          Thread.currentThread().setContextClassLoader(prevLoader);
        }
      }

      // Copied from hadoop-auth's AuthenticationFilter, to allow us to change error response handling in doFilterSuper
      @Override
      protected AuthenticationToken getToken(HttpServletRequest request) throws AuthenticationException
      {
        AuthenticationToken token = null;
        String tokenStr = null;
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
          for (Cookie cookie : cookies) {
            if (cookie.getName().equals(AuthenticatedURL.AUTH_COOKIE)) {
              tokenStr = cookie.getValue();
              try {
                tokenStr = mySigner.verifyAndExtract(tokenStr);
              }
              catch (SignerException ex) {
                throw new AuthenticationException(ex);
              }
              break;
            }
          }
        }
        if (tokenStr != null) {
          token = AuthenticationToken.parse(tokenStr);
          if (!token.getType().equals(getAuthenticationHandler().getType())) {
            throw new AuthenticationException("Invalid AuthenticationToken type");
          }
          if (token.isExpired()) {
            throw new AuthenticationException("AuthenticationToken expired");
          }
        }
        return token;
      }

      @Override
      public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
          throws IOException, ServletException
      {
        // If there's already an auth result, then we have authenticated already, skip this.
        if (request.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT) != null) {
          filterChain.doFilter(request, response);
          return;
        }

        // In the hadoop-auth 2.7.3 code that this was adapted from, the login would've occurred during init() of
        // the AuthenticationFilter via `initializeAuthHandler(authHandlerClassName, filterConfig)`.
        // Since we co-exist with other authentication schemes, don't login until we've checked that
        // some other Authenticator didn't already validate this request.
        if (loginContext == null) {
          initializeKerberosLogin();
        }

        // Run the original doFilter method, but with modifications to error handling
        doFilterSuper(request, response, filterChain);
      }


      /**
       * Copied from hadoop-auth 2.7.3 AuthenticationFilter, to allow us to change error response handling.
       * Specifically, we want to defer the sending of 401 Unauthorized so that other Authenticators later in the chain
       * can check the request.
       */
      private void doFilterSuper(ServletRequest request, ServletResponse response, FilterChain filterChain)
          throws IOException, ServletException
      {
        boolean unauthorizedResponse = true;
        int errCode = HttpServletResponse.SC_UNAUTHORIZED;
        AuthenticationException authenticationEx = null;
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        boolean isHttps = "https".equals(httpRequest.getScheme());
        try {
          boolean newToken = false;
          AuthenticationToken token;
          try {
            token = getToken(httpRequest);
          }
          catch (AuthenticationException ex) {
            log.warn("AuthenticationToken ignored: " + ex.getMessage());
            // will be sent back in a 401 unless filter authenticates
            authenticationEx = ex;
            token = null;
          }
          if (getAuthenticationHandler().managementOperation(token, httpRequest, httpResponse)) {
            if (token == null) {
              if (log.isDebugEnabled()) {
                log.debug("Request [{%s}] triggering authentication", getRequestURL(httpRequest));
              }
              token = getAuthenticationHandler().authenticate(httpRequest, httpResponse);
              if (token != null && token.getExpires() != 0 &&
                  token != AuthenticationToken.ANONYMOUS) {
                token.setExpires(System.currentTimeMillis() + getValidity() * 1000);
              }
              newToken = true;
            }
            if (token != null) {
              unauthorizedResponse = false;
              if (log.isDebugEnabled()) {
                log.debug("Request [{%s}] user [{%s}] authenticated", getRequestURL(httpRequest), token.getUserName());
              }
              final AuthenticationToken authToken = token;
              httpRequest = new HttpServletRequestWrapper(httpRequest)
              {

                @Override
                public String getAuthType()
                {
                  return authToken.getType();
                }

                @Override
                public String getRemoteUser()
                {
                  return authToken.getUserName();
                }

                @Override
                public Principal getUserPrincipal()
                {
                  return (authToken != AuthenticationToken.ANONYMOUS) ? authToken : null;
                }
              };
              if (newToken && !token.isExpired() && token != AuthenticationToken.ANONYMOUS) {
                String signedToken = mySigner.sign(token.toString());
                tokenToAuthCookie(
                    httpResponse,
                    signedToken,
                    getCookieDomain(),
                    getCookiePath(),
                    token.getExpires(),
                    !token.isExpired() && token.getExpires() > 0,
                    isHttps
                );
                request.setAttribute(SIGNED_TOKEN_ATTRIBUTE, tokenToCookieString(
                    signedToken,
                    getCookieDomain(),
                    getCookiePath(),
                    token.getExpires(),
                    !token.isExpired() && token.getExpires() > 0,
                    isHttps
                ));
              }
              // Since this request is validated also set DRUID_AUTHENTICATION_RESULT
              request.setAttribute(
                  AuthConfig.DRUID_AUTHENTICATION_RESULT,
                  new AuthenticationResult(token.getName(), authorizerName, name, null)
              );
              doFilter(filterChain, httpRequest, httpResponse);
            }
          } else {
            unauthorizedResponse = false;
          }
        }
        catch (AuthenticationException ex) {
          // exception from the filter itself is fatal
          errCode = HttpServletResponse.SC_FORBIDDEN;
          authenticationEx = ex;
          if (log.isDebugEnabled()) {
            log.debug(ex, "Authentication exception: " + ex.getMessage());
          } else {
            log.warn("Authentication exception: " + ex.getMessage());
          }
        }
        if (unauthorizedResponse) {
          if (!httpResponse.isCommitted()) {
            tokenToAuthCookie(
                httpResponse,
                "",
                getCookieDomain(),
                getCookiePath(),
                0,
                false,
                isHttps
            );
            // If response code is 401. Then WWW-Authenticate Header should be
            // present.. reset to 403 if not found..
            if ((errCode == HttpServletResponse.SC_UNAUTHORIZED)
                && (!httpResponse.containsHeader(
                org.apache.hadoop.security.authentication.client.KerberosAuthenticator.WWW_AUTHENTICATE))) {
              errCode = HttpServletResponse.SC_FORBIDDEN;
            }
            if (authenticationEx == null) {
              // Don't send an error response here, unlike the base AuthenticationFilter implementation.
              // This request did not use Kerberos auth.
              // Instead, we will send an error response in PreResponseAuthorizationCheckFilter to allow
              // other Authenticator implementations to check the request.
              filterChain.doFilter(request, response);
            } else {
              // Do send an error response here, we attempted Kerberos authentication and failed.
              httpResponse.sendError(errCode, authenticationEx.getMessage());
            }
          }
        }
      }
    };
  }