diff --git a/distribution/pom.xml b/distribution/pom.xml
index 136935433e93..18c1da344a97 100644
--- a/distribution/pom.xml
+++ b/distribution/pom.xml
@@ -252,6 +252,8 @@
-corg.apache.druid.extensions:druid-pac4j-c
+ org.apache.druid.extensions:druid-pac4j-v5
+ -corg.apache.druid.extensions:druid-ranger-security-corg.apache.druid.extensions:druid-kubernetes-extensions
diff --git a/docs/configuration/extensions.md b/docs/configuration/extensions.md
index d396bc29000d..7000c3a4425e 100644
--- a/docs/configuration/extensions.md
+++ b/docs/configuration/extensions.md
@@ -61,7 +61,8 @@ Core extensions are maintained by Druid committers.
|mysql-metadata-storage|MySQL metadata store.|[link](../development/extensions-core/mysql.md)|
|postgresql-metadata-storage|PostgreSQL metadata store.|[link](../development/extensions-core/postgresql.md)|
|simple-client-sslcontext|Simple SSLContext provider module to be used by Druid's internal HttpClient when talking to other Druid processes over HTTPS.|[link](../development/extensions-core/simple-client-sslcontext.md)|
-|druid-pac4j|OpenID Connect authentication for druid processes.|[link](../development/extensions-core/druid-pac4j.md)|
+|druid-pac4j|OpenID Connect authentication for druid processes.|[link](../development/extensions-core/druid-pac4j.md)| | [link](../development/extensions-core/druid-pac4j.md) |
+| druid-pac4j-v5| OpenID Connect authentication for druid processes. Uses version 5 or above for pac4j. Incompatible with JDK8. | | [link](../development/extensions-core/druid-pac4j-v5.md) |
|druid-kubernetes-extensions|Druid cluster deployment on Kubernetes without Zookeeper.|[link](../development/extensions-core/kubernetes.md)|
## Community extensions
diff --git a/docs/development/extensions-core/druid-pac4j-v5.md b/docs/development/extensions-core/druid-pac4j-v5.md
new file mode 100644
index 000000000000..65e75611587e
--- /dev/null
+++ b/docs/development/extensions-core/druid-pac4j-v5.md
@@ -0,0 +1,57 @@
+---
+id: druid-pac4j-v5
+title: "Druid pac4j based Security extension which uses pac4j v5+"
+---
+
+
+
+
+Apache Druid Extension to enable [OpenID Connect](https://openid.net/connect/) based Authentication for Druid Processes using [pac4j](https://github.com/pac4j/pac4j) as the underlying client library.
+This can be used with any authentication server that supports same e.g. [Okta](https://developer.okta.com/).
+The pac4j authenticator should only be used at the router node to enable a group of users in existing authentication server to interact with Druid cluster, using the [web console](../../operations/web-console.md).
+
+This extension also provides a JWT authenticator that validates [ID Tokens](https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken) associated with a request. ID Tokens are attached to the request under the `Authorization` header with the bearer token prefix - `Bearer `. This authenticator is intended for services to talk to Druid by initially authenticating with an OIDC server to retrieve the ID Token which is then attached to every Druid request.
+
+This extension does not support JDBC client authentication.
+
+## Configuration
+
+### Creating an Authenticator
+```
+#Create a pac4j web user authenticator
+druid.auth.authenticatorChain=["pac4j"]
+druid.auth.authenticator.pac4j.type=pac4j
+
+#Create a JWT token authenticator
+druid.auth.authenticatorChain=["jwt"]
+druid.auth.authenticator.jwt.type=jwt
+```
+
+### Properties
+|Property|Description|Default|required|
+|--------|---------------|-----------|-------|
+|`druid.auth.pac4j.cookiePassphrase`|passphrase for encrypting the cookies used to manage authentication session with browser. It can be provided as plaintext string or The [Password Provider](../../operations/password-provider.md).|none|Yes|
+|`druid.auth.pac4j.readTimeout`|Socket connect and read timeout duration used when communicating with authentication server|PT5S|No|
+|`druid.auth.pac4j.enableCustomSslContext`|Whether to use custom SSLContext setup via [simple-client-sslcontext](simple-client-sslcontext.md) extension which must be added to extensions list when this property is set to true.|false|No|
+|`druid.auth.pac4j.oidc.clientID`|OAuth Client Application id.|none|Yes|
+|`druid.auth.pac4j.oidc.clientSecret`|OAuth Client Application secret. It can be provided as plaintext string or The [Password Provider](../../operations/password-provider.md).|none|Yes|
+|`druid.auth.pac4j.oidc.discoveryURI`|discovery URI for fetching OP metadata [see this](http://openid.net/specs/openid-connect-discovery-1_0.html).|none|Yes|
+|`druid.auth.pac4j.oidc.oidcClaim`|[claim](https://openid.net/specs/openid-connect-core-1_0.html#Claims) that will be extracted from the ID Token after validation.|name|No|
+|`druid.auth.pac4j.oidc.scope`| scope is used by an application during authentication to authorize access to a user's details |`openid profile email`|No|
diff --git a/extensions-core/druid-pac4j-v5/pom.xml b/extensions-core/druid-pac4j-v5/pom.xml
new file mode 100644
index 000000000000..f6b6cb6af49b
--- /dev/null
+++ b/extensions-core/druid-pac4j-v5/pom.xml
@@ -0,0 +1,176 @@
+
+
+
+
+ 4.0.0
+
+ org.apache.druid.extensions
+ druid-pac4j-v5
+ druid-pac4j-v5
+ druid-pac4j-v5
+
+
+ org.apache.druid
+ druid
+ 31.0.0-SNAPSHOT
+ ../../pom.xml
+
+
+
+ 11
+ 5.7.3
+
+
+ 1.7
+ 9.37.2
+ 10.1
+
+
+
+
+ org.apache.druid
+ druid-server
+ ${project.parent.version}
+ provided
+
+
+ joda-time
+ joda-time
+ provided
+
+
+ org.apache.druid
+ druid-processing
+ ${project.parent.version}
+ provided
+
+
+ org.pac4j
+ pac4j-oidc
+ ${pac4j.version}
+
+
+
+ org.mockito
+ mockito-core
+
+
+
+
+
+ com.nimbusds
+ lang-tag
+ ${nimbus.lang.tag.version}
+
+
+ com.nimbusds
+ nimbus-jose-jwt
+ ${nimbus.jose.jwt.version}
+
+
+ com.nimbusds
+ oauth2-oidc-sdk
+ ${oauth2.oidc.sdk.version}
+
+
+
+ com.google.code.findbugs
+ jsr305
+ provided
+
+
+ com.google.guava
+ guava
+ provided
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+ provided
+
+
+ com.google.inject
+ guice
+ provided
+
+
+ javax.servlet
+ javax.servlet-api
+ provided
+
+
+ commons-io
+ commons-io
+ provided
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ provided
+
+
+ org.pac4j
+ pac4j-core
+ ${pac4j.version}
+
+
+ org.pac4j
+ pac4j-javaee
+ ${pac4j.version}
+
+
+ javax.ws.rs
+ jsr311-api
+ provided
+
+
+
+ junit
+ junit
+ test
+
+
+ org.easymock
+ easymock
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+
+ com.nimbusds:lang-tag
+
+
+
+
+
+
+
diff --git a/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/CustomSSLResourceRetriever.java b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/CustomSSLResourceRetriever.java
new file mode 100644
index 000000000000..e3ba9eeda34b
--- /dev/null
+++ b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/CustomSSLResourceRetriever.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.security.pac4j;
+
+
+import com.google.common.primitives.Ints;
+import com.nimbusds.jose.util.DefaultResourceRetriever;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSocketFactory;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+
+/**
+ * This class exists only to enable use of custom SSLSocketFactory on top of builtin class. This could be removed
+ * when same functionality has been added to original class com.nimbusds.jose.util.DefaultResourceRetriever.
+ */
+public class CustomSSLResourceRetriever extends DefaultResourceRetriever
+{
+ private SSLSocketFactory sslSocketFactory;
+
+ public CustomSSLResourceRetriever(long readTimeout, SSLSocketFactory sslSocketFactory)
+ {
+ // super(..) has to be the very first statement in constructor.
+ super(Ints.checkedCast(readTimeout), Ints.checkedCast(readTimeout));
+
+ this.sslSocketFactory = sslSocketFactory;
+ }
+
+ @Override
+ protected HttpURLConnection openConnection(final URL url) throws IOException
+ {
+ HttpURLConnection con = super.openConnection(url);
+
+ if (sslSocketFactory != null && con instanceof HttpsURLConnection) {
+ ((HttpsURLConnection) con).setSSLSocketFactory(sslSocketFactory);
+ }
+
+ return con;
+ }
+}
diff --git a/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/JwtAuthFilter.java b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/JwtAuthFilter.java
new file mode 100644
index 000000000000..a0132615ff5f
--- /dev/null
+++ b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/JwtAuthFilter.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.security.pac4j;
+
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.proc.BadJOSEException;
+import com.nimbusds.jwt.JWTParser;
+import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;
+import org.apache.druid.java.util.common.logger.Logger;
+import org.apache.druid.server.security.AuthConfig;
+import org.apache.druid.server.security.AuthenticationResult;
+import org.pac4j.core.context.HttpConstants;
+import org.pac4j.oidc.profile.creator.TokenValidator;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.Optional;
+
+public class JwtAuthFilter implements Filter
+{
+ private static final Logger LOG = new Logger(JwtAuthFilter.class);
+
+ private final String authorizerName;
+ private final String name;
+ private final OIDCConfig oidcConfig;
+ private final TokenValidator tokenValidator;
+
+ public JwtAuthFilter(String authorizerName, String name, OIDCConfig oidcConfig, TokenValidator tokenValidator)
+ {
+ this.authorizerName = authorizerName;
+ this.name = name;
+ this.oidcConfig = oidcConfig;
+ this.tokenValidator = tokenValidator;
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig)
+ {
+
+ }
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
+ throws IOException, ServletException
+ {
+ // Skip this filter if the request has already been authenticated
+ if (servletRequest.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT) != null) {
+ filterChain.doFilter(servletRequest, servletResponse);
+ return;
+ }
+
+ HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
+ HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
+ Optional idToken = extractBearerToken(httpServletRequest);
+
+ if (idToken.isPresent()) {
+ try {
+ // Parses the JWT and performs the ID Token validation specified in the OpenID spec: https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
+ IDTokenClaimsSet claims = tokenValidator.validate(JWTParser.parse(idToken.get()), null);
+ if (claims != null) {
+ Optional claim = Optional.ofNullable(claims.getStringClaim(oidcConfig.getOidcClaim()));
+
+ if (claim.isPresent()) {
+ LOG.debug("Authentication successful for " + oidcConfig.getClientID());
+ AuthenticationResult authenticationResult = new AuthenticationResult(
+ claim.get(),
+ authorizerName,
+ name,
+ null
+ );
+ servletRequest.setAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT, authenticationResult);
+ } else {
+ LOG.error(
+ "Authentication failed! Please ensure that the ID token is valid and it contains the configured claim.");
+ httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ return;
+ }
+ }
+ }
+ catch (BadJOSEException | JOSEException | ParseException e) {
+ LOG.error(e, "Failed to parse JWT token");
+ }
+ }
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+
+
+ @Override
+ public void destroy()
+ {
+
+ }
+
+ private static Optional extractBearerToken(HttpServletRequest request)
+ {
+ String header = request.getHeader(HttpConstants.AUTHORIZATION_HEADER);
+ if (header == null || !header.startsWith(HttpConstants.BEARER_HEADER_PREFIX)) {
+ LOG.debug("Request does not contain bearer authentication scheme");
+ return Optional.empty();
+ }
+ String headerWithoutPrefix = header.substring(HttpConstants.BEARER_HEADER_PREFIX.length());
+ return Optional.of(headerWithoutPrefix);
+ }
+}
diff --git a/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/JwtAuthenticator.java b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/JwtAuthenticator.java
new file mode 100644
index 000000000000..96c82a491a1b
--- /dev/null
+++ b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/JwtAuthenticator.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.security.pac4j;
+
+import com.fasterxml.jackson.annotation.JacksonInject;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import org.apache.druid.server.security.AuthenticationResult;
+import org.apache.druid.server.security.Authenticator;
+import org.pac4j.oidc.config.OidcConfiguration;
+import org.pac4j.oidc.profile.creator.TokenValidator;
+
+import javax.annotation.Nullable;
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import java.util.EnumSet;
+import java.util.Map;
+
+@JsonTypeName("jwt")
+public class JwtAuthenticator implements Authenticator
+{
+ private final String authorizerName;
+ private final OIDCConfig oidcConfig;
+ private final Supplier tokenValidatorSupplier;
+ private final String name;
+
+ @JsonCreator
+ public JwtAuthenticator(
+ @JsonProperty("name") String name,
+ @JsonProperty("authorizerName") String authorizerName,
+ @JacksonInject OIDCConfig oidcConfig
+ )
+ {
+ this.name = name;
+ this.oidcConfig = oidcConfig;
+ this.authorizerName = authorizerName;
+
+ this.tokenValidatorSupplier = Suppliers.memoize(() -> createTokenValidator(oidcConfig));
+ }
+
+ @Override
+ public Filter getFilter()
+ {
+ return new JwtAuthFilter(authorizerName, name, oidcConfig, tokenValidatorSupplier.get());
+ }
+
+ @Override
+ public Class extends Filter> getFilterClass()
+ {
+ return JwtAuthFilter.class;
+ }
+
+ @Override
+ public Map getInitParameters()
+ {
+ return null;
+ }
+
+ @Override
+ public String getPath()
+ {
+ return "/*";
+ }
+
+ @Nullable
+ @Override
+ public EnumSet getDispatcherType()
+ {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public String getAuthChallengeHeader()
+ {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public AuthenticationResult authenticateJDBCContext(Map context)
+ {
+ return null;
+ }
+
+ private TokenValidator createTokenValidator(OIDCConfig config)
+ {
+ OidcConfiguration oidcConfiguration = new OidcConfiguration();
+ oidcConfiguration.setClientId(config.getClientID());
+ oidcConfiguration.setSecret(config.getClientSecret().getPassword());
+ oidcConfiguration.setDiscoveryURI(config.getDiscoveryURI());
+ return new TokenValidator(oidcConfiguration);
+ }
+}
diff --git a/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/OIDCConfig.java b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/OIDCConfig.java
new file mode 100644
index 000000000000..376181416556
--- /dev/null
+++ b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/OIDCConfig.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.security.pac4j;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import org.apache.druid.metadata.PasswordProvider;
+
+import javax.annotation.Nullable;
+
+public class OIDCConfig
+{
+ private final String DEFAULT_SCOPE = "name";
+ @JsonProperty
+ private final String clientID;
+
+ @JsonProperty
+ private final PasswordProvider clientSecret;
+
+ @JsonProperty
+ private final String discoveryURI;
+
+ @JsonProperty
+ private final String oidcClaim;
+
+ @JsonProperty
+ private final String scope;
+
+ @JsonCreator
+ public OIDCConfig(
+ @JsonProperty("clientID") String clientID,
+ @JsonProperty("clientSecret") PasswordProvider clientSecret,
+ @JsonProperty("discoveryURI") String discoveryURI,
+ @JsonProperty("oidcClaim") String oidcClaim,
+ @JsonProperty("scope") @Nullable String scope
+ )
+ {
+ this.clientID = Preconditions.checkNotNull(clientID, "null clientID");
+ this.clientSecret = Preconditions.checkNotNull(clientSecret, "null clientSecret");
+ this.discoveryURI = Preconditions.checkNotNull(discoveryURI, "null discoveryURI");
+ this.oidcClaim = oidcClaim == null ? DEFAULT_SCOPE : oidcClaim;
+ this.scope = scope;
+ }
+
+ @JsonProperty
+ public String getClientID()
+ {
+ return clientID;
+ }
+
+ @JsonProperty
+ public PasswordProvider getClientSecret()
+ {
+ return clientSecret;
+ }
+
+ @JsonProperty
+ public String getDiscoveryURI()
+ {
+ return discoveryURI;
+ }
+
+ @JsonProperty
+ public String getOidcClaim()
+ {
+ return oidcClaim;
+ }
+
+ @JsonProperty
+ public String getScope()
+ {
+ return scope;
+ }
+}
diff --git a/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jAuthenticator.java b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jAuthenticator.java
new file mode 100644
index 000000000000..b63fcdf72775
--- /dev/null
+++ b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jAuthenticator.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.security.pac4j;
+
+import com.fasterxml.jackson.annotation.JacksonInject;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.primitives.Ints;
+import com.google.inject.Provider;
+import com.nimbusds.oauth2.sdk.http.HTTPRequest;
+import org.apache.druid.server.security.AuthenticationResult;
+import org.apache.druid.server.security.Authenticator;
+import org.pac4j.core.config.Config;
+import org.pac4j.core.http.callback.NoParameterCallbackUrlResolver;
+import org.pac4j.core.http.url.DefaultUrlResolver;
+import org.pac4j.oidc.client.OidcClient;
+import org.pac4j.oidc.config.OidcConfiguration;
+
+import javax.annotation.Nullable;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import java.util.EnumSet;
+import java.util.Map;
+
+@JsonTypeName("pac4j")
+public class Pac4jAuthenticator implements Authenticator
+{
+ private final String name;
+ private final String authorizerName;
+ private final Supplier pac4jConfigSupplier;
+ private final Pac4jCommonConfig pac4jCommonConfig;
+ private final SSLSocketFactory sslSocketFactory;
+
+ @JsonCreator
+ public Pac4jAuthenticator(
+ @JsonProperty("name") String name,
+ @JsonProperty("authorizerName") String authorizerName,
+ @JacksonInject Pac4jCommonConfig pac4jCommonConfig,
+ @JacksonInject OIDCConfig oidcConfig,
+ @JacksonInject Provider sslContextSupplier
+ )
+ {
+ this.name = name;
+ this.authorizerName = authorizerName;
+ this.pac4jCommonConfig = pac4jCommonConfig;
+
+ if (pac4jCommonConfig.isEnableCustomSslContext()) {
+ this.sslSocketFactory = sslContextSupplier.get().getSocketFactory();
+ } else {
+ this.sslSocketFactory = null;
+ }
+
+ this.pac4jConfigSupplier = Suppliers.memoize(() -> createPac4jConfig(oidcConfig));
+ }
+
+ @Override
+ public Filter getFilter()
+ {
+ return new Pac4jFilter(
+ name,
+ authorizerName,
+ pac4jConfigSupplier.get(),
+ pac4jCommonConfig.getCookiePassphrase().getPassword()
+ );
+ }
+
+ @Override
+ public String getAuthChallengeHeader()
+ {
+ return null;
+ }
+
+ @Override
+ @Nullable
+ public AuthenticationResult authenticateJDBCContext(Map context)
+ {
+ return null;
+ }
+
+
+ @Override
+ public Class extends Filter> getFilterClass()
+ {
+ return null;
+ }
+
+ @Override
+ public Map getInitParameters()
+ {
+ return null;
+ }
+
+ @Override
+ public String getPath()
+ {
+ return "/*";
+ }
+
+ @Override
+ public EnumSet getDispatcherType()
+ {
+ return null;
+ }
+
+ private Config createPac4jConfig(OIDCConfig oidcConfig)
+ {
+ OidcConfiguration oidcConf = new OidcConfiguration();
+ oidcConf.setClientId(oidcConfig.getClientID());
+ oidcConf.setSecret(oidcConfig.getClientSecret().getPassword());
+ oidcConf.setDiscoveryURI(oidcConfig.getDiscoveryURI());
+ oidcConf.setScope(oidcConfig.getScope());
+ oidcConf.setExpireSessionWithToken(true);
+ oidcConf.setUseNonce(true);
+ oidcConf.setReadTimeout(Ints.checkedCast(pac4jCommonConfig.getReadTimeout().getMillis()));
+
+ oidcConf.setResourceRetriever(
+ // ResourceRetriever is used to get Auth server configuration from "discoveryURI"
+ new CustomSSLResourceRetriever(pac4jCommonConfig.getReadTimeout().getMillis(), sslSocketFactory)
+ );
+
+ OidcClient oidcClient = new OidcClient(oidcConf);
+ oidcClient.setUrlResolver(new DefaultUrlResolver(true));
+ oidcClient.setCallbackUrlResolver(new NoParameterCallbackUrlResolver());
+
+ // This is used by OidcClient in various places to make HTTPrequests.
+ if (sslSocketFactory != null) {
+ HTTPRequest.setDefaultSSLSocketFactory(sslSocketFactory);
+ }
+
+ return new Config(Pac4jCallbackResource.SELF_URL, oidcClient);
+ }
+}
diff --git a/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jCallbackResource.java b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jCallbackResource.java
new file mode 100644
index 000000000000..e975b7cfdd3c
--- /dev/null
+++ b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jCallbackResource.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.security.pac4j;
+
+import com.google.inject.Inject;
+import org.apache.druid.guice.LazySingleton;
+import org.apache.druid.java.util.common.logger.Logger;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Response;
+
+/**
+ * Fixed Callback endpoint used after successful login with Identity Provider e.g. OAuth server.
+ * See https://www.pac4j.org/blog/understanding-the-callback-endpoint.html
+ */
+@Path(Pac4jCallbackResource.SELF_URL)
+@LazySingleton
+public class Pac4jCallbackResource
+{
+ public static final String SELF_URL = "/druid-ext/druid-pac4j/callback";
+
+ private static final Logger LOGGER = new Logger(Pac4jCallbackResource.class);
+
+ @Inject
+ public Pac4jCallbackResource()
+ {
+ }
+
+ @GET
+ public Response callback()
+ {
+ LOGGER.error(
+ new RuntimeException(),
+ "This endpoint is to be handled by the pac4j filter to redirect users, request should never reach here."
+ );
+ return Response.serverError().build();
+ }
+}
+
diff --git a/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jCommonConfig.java b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jCommonConfig.java
new file mode 100644
index 000000000000..0a57869fb57e
--- /dev/null
+++ b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jCommonConfig.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.security.pac4j;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import org.apache.druid.metadata.PasswordProvider;
+import org.joda.time.Duration;
+
+public class Pac4jCommonConfig
+{
+ @JsonProperty
+ private final boolean enableCustomSslContext;
+
+ @JsonProperty
+ private final PasswordProvider cookiePassphrase;
+
+ @JsonProperty
+ private final Duration readTimeout;
+
+ @JsonCreator
+ public Pac4jCommonConfig(
+ @JsonProperty("enableCustomSslContext") boolean enableCustomSslContext,
+ @JsonProperty("cookiePassphrase") PasswordProvider cookiePassphrase,
+ @JsonProperty("readTimeout") Duration readTimeout
+ )
+ {
+ this.enableCustomSslContext = enableCustomSslContext;
+ this.cookiePassphrase = Preconditions.checkNotNull(cookiePassphrase, "null cookiePassphrase");
+ this.readTimeout = readTimeout == null ? Duration.millis(5000) : readTimeout;
+ }
+
+ @JsonProperty
+ public boolean isEnableCustomSslContext()
+ {
+ return enableCustomSslContext;
+ }
+
+ @JsonProperty
+ public PasswordProvider getCookiePassphrase()
+ {
+ return cookiePassphrase;
+ }
+
+ @JsonProperty
+ public Duration getReadTimeout()
+ {
+ return readTimeout;
+ }
+}
diff --git a/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jDruidModule.java b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jDruidModule.java
new file mode 100644
index 000000000000..f7113ab67a58
--- /dev/null
+++ b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jDruidModule.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.security.pac4j;
+
+import com.fasterxml.jackson.databind.Module;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.google.common.collect.ImmutableList;
+import com.google.inject.Binder;
+import org.apache.druid.guice.Jerseys;
+import org.apache.druid.guice.JsonConfigProvider;
+import org.apache.druid.initialization.DruidModule;
+
+import java.util.List;
+
+public class Pac4jDruidModule implements DruidModule
+{
+ @Override
+ public List extends Module> getJacksonModules()
+ {
+ return ImmutableList.of(
+ new SimpleModule("Pac4jDruidSecurity").registerSubtypes(
+ Pac4jAuthenticator.class,
+ JwtAuthenticator.class
+ )
+ );
+ }
+
+ @Override
+ public void configure(Binder binder)
+ {
+ JsonConfigProvider.bind(binder, "druid.auth.pac4j", Pac4jCommonConfig.class);
+ JsonConfigProvider.bind(binder, "druid.auth.pac4j.oidc", OIDCConfig.class);
+
+ Jerseys.addResource(binder, Pac4jCallbackResource.class);
+ }
+}
diff --git a/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jFilter.java b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jFilter.java
new file mode 100644
index 000000000000..2f3b1182ccb4
--- /dev/null
+++ b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jFilter.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.security.pac4j;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.druid.java.util.common.logger.Logger;
+import org.apache.druid.server.security.AuthConfig;
+import org.apache.druid.server.security.AuthenticationResult;
+import org.pac4j.core.config.Config;
+import org.pac4j.core.context.session.SessionStore;
+import org.pac4j.core.engine.CallbackLogic;
+import org.pac4j.core.engine.DefaultCallbackLogic;
+import org.pac4j.core.engine.DefaultSecurityLogic;
+import org.pac4j.core.engine.SecurityLogic;
+import org.pac4j.core.profile.UserProfile;
+import org.pac4j.jee.context.JEEContext;
+import org.pac4j.jee.http.adapter.JEEHttpActionAdapter;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import java.io.IOException;
+
+public class Pac4jFilter implements Filter
+{
+ private static final Logger LOGGER = new Logger(Pac4jFilter.class);
+
+ private final Config pac4jConfig;
+ private final SecurityLogic securityLogic;
+ private final CallbackLogic callbackLogic;
+ private final SessionStore sessionStore;
+
+ private final String name;
+ private final String authorizerName;
+
+ public Pac4jFilter(String name, String authorizerName, Config pac4jConfig, String cookiePassphrase)
+ {
+ this.pac4jConfig = pac4jConfig;
+ this.securityLogic = new DefaultSecurityLogic();
+ this.callbackLogic = new DefaultCallbackLogic();
+
+ this.name = name;
+ this.authorizerName = authorizerName;
+
+ this.sessionStore = new Pac4jSessionStore<>(cookiePassphrase);
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig)
+ {
+ }
+
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
+ throws IOException, ServletException
+ {
+ // If there's already an auth result, then we have authenticated already, skip this or else caller
+ // could get HTTP redirect even if one of the druid authenticators in chain has successfully authenticated.
+ if (servletRequest.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT) != null) {
+ filterChain.doFilter(servletRequest, servletResponse);
+ return;
+ }
+
+ HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
+ HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
+ JEEContext context = new JEEContext(httpServletRequest, httpServletResponse);
+
+ if (Pac4jCallbackResource.SELF_URL.equals(httpServletRequest.getRequestURI())) {
+ callbackLogic.perform(
+ context,
+ sessionStore,
+ pac4jConfig,
+ JEEHttpActionAdapter.INSTANCE,
+ "/", true, null);
+ } else {
+ UserProfile profile = (UserProfile) securityLogic.perform(
+ context,
+ sessionStore,
+ pac4jConfig, null,
+ JEEHttpActionAdapter.INSTANCE,
+ null, "none", null);
+ // Changed the Authorizer from null to "none".
+ // In the older version, if it is null, it simply grant access and returns authorized.
+ // But in the newer pac4j version, it uses CsrfAuthorizer as default, And because of this, It was returning 403 in API calls.
+ if (profile != null && profile.getId() != null) {
+ AuthenticationResult authenticationResult = new AuthenticationResult(profile.getId(), authorizerName, name, ImmutableMap.of("profile", profile));
+ servletRequest.setAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT, authenticationResult);
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+ }
+ }
+
+ @Override
+ public void destroy()
+ {
+ }
+}
diff --git a/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jSessionStore.java b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jSessionStore.java
new file mode 100644
index 000000000000..9a9d71ef440d
--- /dev/null
+++ b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jSessionStore.java
@@ -0,0 +1,212 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.security.pac4j;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.druid.crypto.CryptoService;
+import org.apache.druid.java.util.common.StringUtils;
+import org.apache.druid.java.util.common.logger.Logger;
+import org.pac4j.core.context.Cookie;
+import org.pac4j.core.context.WebContext;
+import org.pac4j.core.context.WebContextHelper;
+import org.pac4j.core.context.session.SessionStore;
+import org.pac4j.core.exception.TechnicalException;
+import org.pac4j.core.profile.CommonProfile;
+import org.pac4j.core.util.Pac4jConstants;
+import org.pac4j.core.util.serializer.JavaSerializer;
+
+import javax.annotation.Nullable;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.Map;
+import java.util.Optional;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * Code here is slight adaptation from KnoxSessionStore
+ * for storing oauth session information in cookies.
+ */
+public class Pac4jSessionStore implements SessionStore
+{
+
+ private static final Logger LOGGER = new Logger(Pac4jSessionStore.class);
+
+ public static final String PAC4J_SESSION_PREFIX = "pac4j.session.";
+
+ private final JavaSerializer javaSerializer;
+ private final CryptoService cryptoService;
+
+ public Pac4jSessionStore(String cookiePassphrase)
+ {
+ javaSerializer = new JavaSerializer();
+ cryptoService = new CryptoService(
+ cookiePassphrase,
+ "AES",
+ "CBC",
+ "PKCS5Padding",
+ "PBKDF2WithHmacSHA256",
+ 128,
+ 65536,
+ 128
+ );
+ }
+
+ @Override
+ public Optional getSessionId(WebContext webContext, boolean b)
+ {
+ return Optional.empty();
+ }
+
+ @Nullable
+ @Override
+ public Optional