diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/security/authentication.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/security/authentication.adoc index ad1b1e6c49b..abd1b13b8b8 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/security/authentication.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/security/authentication.adoc @@ -24,7 +24,7 @@ Authorization:: ==== Configuring an Authentication mechanism -Jetty server supports several standard authentication mechanisms: http://en.wikipedia.org/wiki/Basic_access_authentication[BASIC]; http://en.wikipedia.org/wiki/Digest_authentication[DIGEST]; http://en.wikipedia.org/wiki/Form-based_authentication[FORM]; CLIENT-CERT; and other mechanisms can be plugged in using the extensible http://docs.oracle.com/cd/E19462-01/819-6717/gcszc/index.html[JASPI] or http://en.wikipedia.org/wiki/SPNEGO[SPNEGO] mechanisms. +Jetty server supports several standard authentication mechanisms: http://en.wikipedia.org/wiki/Basic_access_authentication[BASIC]; http://en.wikipedia.org/wiki/Digest_authentication[DIGEST]; http://en.wikipedia.org/wiki/Form-based_authentication[FORM]; CLIENT-CERT; and other mechanisms can be plugged in using the extensible https://jakarta.ee/specifications/authentication/2.0/jakarta-authentication-spec-2.0.pdf[JakartaAuthentication] or http://en.wikipedia.org/wiki/SPNEGO[SPNEGO] mechanisms. Internally, configuring an authentication mechanism is done by setting an instance of a the link:{JDURL}/org/eclipse/jetty/security/Authenticator.html[Authenticator] interface onto the link:{JDURL}/org/eclipse/jetty/security/SecurityHandler.html[SecurityHandler] of the context, but in most cases it is done by declaring a `` element in the standard web.xml descriptor or via annotations. @@ -511,3 +511,19 @@ Below is an example which, like the one above, sets up a server with a `HashLogi ---- include::{SRCDIR}/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java[] ---- + +==== Jakarta Authentication (JASPI) + +Jetty can utilize portable authentication modules that implements the Jakara Authentication specification. This requires the jetty-jaspi module. + +Only modules conforming to the ServerAuthModule interface in the https://jakarta.ee/specifications/authentication/2.0/jakarta-authentication-spec-2.0.pdf[JakartaAuthentication] are supported. These modules must be configured before start-up. + +The following illustrates a jetty module setting up HTTP Basic Authentication using a Jakarta Authentication module that comes packaged with the jetty-jaspi module: `org.eclipse.jetty.security.jaspi.modules.BasicAuthenticationAuthModule` + +[source, xml, subs="{sub-order}"] +---- +include::{SRCDIR}/jetty-jaspi/src/main/config/etc/jaspi/jaspi-demo.xml[tags=documentation] +---- + +Given the portability goal of Jakarta Authentication, custom or 3rd party `ServerAuthModule` implementations may be configured instead here. + diff --git a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/index.adoc b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/index.adoc index aaf01516489..eb3e8507bab 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/index.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/index.adoc @@ -34,6 +34,7 @@ include::annotations/chapter.adoc[] include::jsp/chapter.adoc[] include::jndi/chapter.adoc[] include::jaas/chapter.adoc[] +include::jaspi/chapter.adoc[] include::jmx/chapter.adoc[] include::logging/chapter.adoc[] include::troubleshooting/chapter.adoc[] diff --git a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/jaspi/chapter.adoc b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/jaspi/chapter.adoc new file mode 100644 index 00000000000..412310f6e04 --- /dev/null +++ b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/jaspi/chapter.adoc @@ -0,0 +1,68 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +[[og-jaspi]] +=== JASPI + +Enabling this module allows Jetty to utilize authentication modules that implement the Jakarta Authentication (JASPI) specification. JASPI provides an SPI (Service Provider Interface) for pluggable, portable, and standardized authentication modules. Compatible modules are portable between servers that support the JASPI specification. This module provides a bridge from Jakarta Authentication to the Jetty Security framework. + +Only modules conforming to the "Servlet Container Profile" with the ServerAuthModule interface within the https://jakarta.ee/specifications/authentication/2.0/jakarta-authentication-spec-2.0.pdf[JakartaAuthentication] are supported. These modules must be configured before start-up. Operations for runtime registering or de-registering authentication modules are not supported. + +[[og-jaspi-configuration]] +==== Configuration + +[[og-jaspi-module]] +===== The `jaspi` module + +Enable the `jaspi` module: + +---- +include::{JETTY_HOME}/modules/jaspi.mod[] +---- + +[[og-jaspi-xml]] +===== Configure JASPI + +To enable the `jaspi` module you can use the following command (issued from within the `$JETTY_BASE` directory): + +---- +$ java -jar $JETTY_HOME/start.jar --add-modules=jaspi +---- + +You can then register a `AuthConfigProvider` onto the static `AuthConfigFactory` obtained with `AuthConfigFactory.getFactory()`. This registration can be done in the XML configuration file which will be copied to `$JETTY_BASE/etc/jaspi/jaspi-authmoduleconfig.xml` when the module is enabled. + +====== JASPI Demo +The `jaspi-demo` module illustrates setting up HTTP Basic Authentication using a Jakarta Authentication module that comes packaged with jetty: `org.eclipse.jetty.security.jaspi.modules.BasicAuthenticationAuthModule`, and applies it for a context named `/test`. + +[source, xml] +---- +include::{JETTY_HOME}/etc/jaspi/jaspi-demo.xml[] +---- + +This example uses the `AuthConfigProvider` implementation provided by Jetty to register a `ServerAuthModule` directly. Other custom or 3rd party modules that are compatible with the `ServerAuthModule` interface in JASPI can be registered in the same way. + +===== Integration with Jetty Authentication Mechanisms + +To integrate with Jetty authentication mechanisms you must add a `LoginService` to your context. The `LoginService` provides a way for you to obtain a `UserIdentity` from a username and credentials. JASPI can interact with this Jetty `LoginService` by using the `PasswordValidationCallback`. + +The `CallerPrincipalCallback` and `GroupPrincipalCallback` do not require use of a Jetty `LoginService`. The principal from the `CallerPrincipalCallback` will be used directly with the `IdentityService` to produce a `UserIdentity`. + +===== Replacing the Jetty DefaultAuthConfigFactory + +Jetty provides an implementation of the `AuthConfigFactory` interface which is used to register `AuthConfigProviders`. This can be replaced by a custom implementation by adding a custom module which provides `auth-config-factory`. +This custom module must reference an XML file which sets a new instance of the `AuthConfigFactory` with the static method `AuthConfigFactory.setFactory()`. +For an example of this see the `jaspi-default-auth-config-factory` module, which provides the default implementation used by Jetty. + +---- +include::{JETTY_HOME}/modules/jaspi-default-auth-config-factory.mod[] +---- \ No newline at end of file diff --git a/javadoc/pom.xml b/javadoc/pom.xml index ff8c6c99ef0..0684d408008 100644 --- a/javadoc/pom.xml +++ b/javadoc/pom.xml @@ -438,7 +438,7 @@ com.sun.activation jakarta.activation - 2.0.0 + 2.0.1 provided diff --git a/jetty-home/pom.xml b/jetty-home/pom.xml index f6db614df0a..f217d05d81b 100644 --- a/jetty-home/pom.xml +++ b/jetty-home/pom.xml @@ -409,8 +409,8 @@ copy-dependencies - org.eclipse.jetty.orbit - javax.security.auth.message + jakarta.authentication + jakarta.authentication-api jar ${assembly-directory}/lib/jaspi @@ -422,8 +422,8 @@ copy-dependencies - org.eclipse.jetty.orbit - javax.security.auth.message + jakarta.authentication + jakarta.authentication-api jar sources ${source-assembly-directory}/lib/jaspi @@ -534,12 +534,6 @@ - - - org.eclipse.jetty.orbit - javax.security.auth.message - - jakarta.mail jakarta.mail-api @@ -548,6 +542,10 @@ jakarta.annotation jakarta.annotation-api + + jakarta.authentication + jakarta.authentication-api + jakarta.transaction jakarta.transaction-api diff --git a/jetty-jaspi/pom.xml b/jetty-jaspi/pom.xml index 48315af39ab..16bb0f4e1a4 100644 --- a/jetty-jaspi/pom.xml +++ b/jetty-jaspi/pom.xml @@ -63,37 +63,12 @@ test - org.eclipse.jetty.orbit - javax.security.auth.message + jakarta.authentication + jakarta.authentication-api - javax.xml.bind - jaxb-api - 2.3.1 - test - - - org.glassfish.jaxb - jaxb-runtime - 2.3.3 - test - - - javax.activation - javax.activation-api - 1.2.0 - test - - - org.apache.geronimo.components - geronimo-jaspi - 2.0.0 - - - org.apache.geronimo.specs - geronimo-jaspic_1.0_spec - - + jakarta.activation + jakarta.activation-api test diff --git a/jetty-jaspi/src/main/config/etc/jaspi/jaspi-authmoduleconfig.xml b/jetty-jaspi/src/main/config/etc/jaspi/jaspi-authmoduleconfig.xml new file mode 100644 index 00000000000..189888d9d57 --- /dev/null +++ b/jetty-jaspi/src/main/config/etc/jaspi/jaspi-authmoduleconfig.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/jetty-jaspi/src/main/config/etc/jaspi/jaspi-default.xml b/jetty-jaspi/src/main/config/etc/jaspi/jaspi-default.xml new file mode 100644 index 00000000000..9dbdd89ad0d --- /dev/null +++ b/jetty-jaspi/src/main/config/etc/jaspi/jaspi-default.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + false + + diff --git a/jetty-jaspi/src/main/config/etc/jaspi/jaspi-demo.xml b/jetty-jaspi/src/main/config/etc/jaspi/jaspi-demo.xml new file mode 100644 index 00000000000..8c00a9cdb7d --- /dev/null +++ b/jetty-jaspi/src/main/config/etc/jaspi/jaspi-demo.xml @@ -0,0 +1,48 @@ + + + + + + + + + org.eclipse.jetty.security.jaspi.provider.JaspiAuthConfigProvider + + + + + + + ServerAuthModule + org.eclipse.jetty.security.jaspi.modules.BasicAuthenticationAuthModule + + + + org.eclipse.jetty.security.jaspi.modules.RealmName + Test Realm + + + + + + HttpServlet + + + server /test + + + A simple provider using HTTP BASIC authentication. + + + \ No newline at end of file diff --git a/jetty-jaspi/src/main/config/modules/jaspi-default-auth-config-factory.mod b/jetty-jaspi/src/main/config/modules/jaspi-default-auth-config-factory.mod new file mode 100644 index 00000000000..70913c907be --- /dev/null +++ b/jetty-jaspi/src/main/config/modules/jaspi-default-auth-config-factory.mod @@ -0,0 +1,16 @@ +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html + +[description] +Provides a DefaultAuthConfigFactory for jaspi + +[tags] +security + +[depend] +security + +[provide] +auth-config-factory + +[xml] +etc/jaspi/jaspi-default.xml diff --git a/jetty-jaspi/src/main/config/modules/jaspi-demo.mod b/jetty-jaspi/src/main/config/modules/jaspi-demo.mod new file mode 100644 index 00000000000..7d1b6958b4f --- /dev/null +++ b/jetty-jaspi/src/main/config/modules/jaspi-demo.mod @@ -0,0 +1,16 @@ +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html + +[description] +Enables JASPI basic authentication the /ctx context path. + +[tags] +security + +[depend] +jaspi + +[xml] +etc/jaspi/jaspi-demo.xml + +[files] +basehome:etc/jaspi/jaspi-demo.xml|etc/jaspi/jaspi-demo.xml diff --git a/jetty-jaspi/src/main/config/modules/jaspi.mod b/jetty-jaspi/src/main/config/modules/jaspi.mod index d6573f7b8f4..76b2e81406e 100644 --- a/jetty-jaspi/src/main/config/modules/jaspi.mod +++ b/jetty-jaspi/src/main/config/modules/jaspi.mod @@ -3,9 +3,20 @@ [description] Enables JASPI authentication for deployed web applications. +[tags] +security + [depend] security +auth-config-factory [lib] lib/jetty-jaspi-${jetty.version}.jar lib/jaspi/*.jar + +[xml] +etc/jaspi/jaspi-authmoduleconfig.xml + +[files] +basehome:etc/jaspi/jaspi-authmoduleconfig.xml|etc/jaspi/jaspi-authmoduleconfig.xml + diff --git a/jetty-jaspi/src/main/java/module-info.java b/jetty-jaspi/src/main/java/module-info.java index 67117b0c027..06736e54b6b 100644 --- a/jetty-jaspi/src/main/java/module-info.java +++ b/jetty-jaspi/src/main/java/module-info.java @@ -19,8 +19,9 @@ module org.eclipse.jetty.security.jaspi exports org.eclipse.jetty.security.jaspi; exports org.eclipse.jetty.security.jaspi.callback; exports org.eclipse.jetty.security.jaspi.modules; + exports org.eclipse.jetty.security.jaspi.provider; - requires javax.security.auth.message; + requires transitive jakarta.security.auth.message; requires jetty.servlet.api; requires transitive org.eclipse.jetty.security; requires org.slf4j; diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/DefaultAuthConfigFactory.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/DefaultAuthConfigFactory.java new file mode 100644 index 00000000000..d724eecdf91 --- /dev/null +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/DefaultAuthConfigFactory.java @@ -0,0 +1,248 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.security.jaspi; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +import jakarta.security.auth.message.config.AuthConfigFactory; +import jakarta.security.auth.message.config.AuthConfigProvider; +import jakarta.security.auth.message.config.RegistrationListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A very basic {@link AuthConfigFactory} that allows for registering providers programmatically. + */ +public class DefaultAuthConfigFactory extends AuthConfigFactory +{ + private static final Logger LOG = LoggerFactory.getLogger(DefaultAuthConfigFactory.class); + private final Map _registrations = new ConcurrentHashMap<>(); + + public DefaultAuthConfigFactory() + { + } + + @Override + public AuthConfigProvider getConfigProvider(String layer, String appContext, RegistrationListener listener) + { + DefaultRegistrationContext registrationContext = _registrations.get(getKey(layer, appContext)); + if (registrationContext == null) + registrationContext = _registrations.get(getKey(null, appContext)); + if (registrationContext == null) + registrationContext = _registrations.get(getKey(layer, null)); + if (registrationContext == null) + registrationContext = _registrations.get(getKey(null, null)); + if (registrationContext == null) + return null; + + // TODO: according to the javadoc you're supposed to register listener even if there is no context available. + if (listener != null) + registrationContext.addListener(listener); + return registrationContext.getProvider(); + } + + @Override + public String registerConfigProvider(String className, Map properties, String layer, String appContext, String description) + { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + sm.checkPermission(AuthConfigFactory.providerRegistrationSecurityPermission); + + String key = getKey(layer, appContext); + AuthConfigProvider configProvider = createConfigProvider(className, properties); + DefaultRegistrationContext context = new DefaultRegistrationContext(configProvider, layer, appContext, description, true); + DefaultRegistrationContext oldContext = _registrations.put(key, context); + if (oldContext != null) + oldContext.notifyListeners(); + return key; + } + + @Override + public String registerConfigProvider(AuthConfigProvider provider, String layer, String appContext, String description) + { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + sm.checkPermission(AuthConfigFactory.providerRegistrationSecurityPermission); + + String key = getKey(layer, appContext); + DefaultRegistrationContext context = new DefaultRegistrationContext(provider, layer, appContext, description, false); + DefaultRegistrationContext oldContext = _registrations.put(key, context); + if (oldContext != null) + oldContext.notifyListeners(); + return key; + } + + @Override + public boolean removeRegistration(String registrationID) + { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + sm.checkPermission(AuthConfigFactory.providerRegistrationSecurityPermission); + + DefaultRegistrationContext registrationContext = _registrations.remove(registrationID); + if (registrationContext == null) + return false; + + registrationContext.notifyListeners(); + return true; + } + + @Override + public String[] detachListener(RegistrationListener listener, String layer, String appContext) + { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + sm.checkPermission(AuthConfigFactory.providerRegistrationSecurityPermission); + + List registrationIds = new ArrayList<>(); + for (DefaultRegistrationContext registration : _registrations.values()) + { + if ((layer == null || layer.equals(registration.getMessageLayer())) && (appContext == null || appContext.equals(registration.getAppContext()))) + { + if (registration.removeListener(listener)) + registrationIds.add(getKey(registration.getMessageLayer(), registration.getAppContext())); + } + } + + return registrationIds.toArray(new String[0]); + } + + @Override + public String[] getRegistrationIDs(AuthConfigProvider provider) + { + List registrationIds = new ArrayList<>(); + for (DefaultRegistrationContext registration : _registrations.values()) + { + if (provider == registration.getProvider()) + registrationIds.add(getKey(registration.getMessageLayer(), registration.getAppContext())); + } + + return registrationIds.toArray(new String[0]); + } + + @Override + public RegistrationContext getRegistrationContext(String registrationID) + { + return _registrations.get(registrationID); + } + + @Override + public void refresh() + { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + sm.checkPermission(AuthConfigFactory.providerRegistrationSecurityPermission); + + // TODO: maybe we should re-construct providers created from classname. + } + + private static String getKey(String layer, String appContext) + { + return layer + "/" + appContext; + } + + @SuppressWarnings("rawtypes") + private AuthConfigProvider createConfigProvider(String className, Map properties) + { + try + { + // Javadoc specifies all AuthConfigProvider implementations must have this constructor, and that + // to construct this we must pass a null value for the factory argument of the constructor. + return (AuthConfigProvider)Class.forName(className) + .getConstructor(Map.class, AuthConfigFactory.class) + .newInstance(properties, null); + } + catch (ReflectiveOperationException e) + { + throw new SecurityException(e); + } + } + + private static class DefaultRegistrationContext implements RegistrationContext + { + private final String _layer; + private final String _appContext; + private final boolean _persistent; + private final AuthConfigProvider _provider; + private final String _description; + private final List _listeners = new CopyOnWriteArrayList<>(); + + public DefaultRegistrationContext(AuthConfigProvider provider, String layer, String appContext, String description, boolean persistent) + { + _provider = provider; + _layer = layer; + _appContext = appContext; + _description = description; + _persistent = persistent; + } + + public AuthConfigProvider getProvider() + { + return _provider; + } + + @Override + public String getMessageLayer() + { + return _layer; + } + + @Override + public String getAppContext() + { + return _appContext; + } + + @Override + public String getDescription() + { + return _description; + } + + @Override + public boolean isPersistent() + { + return false; + } + + public void addListener(RegistrationListener listener) + { + _listeners.add(listener); + } + + public void notifyListeners() + { + for (RegistrationListener listener : _listeners) + { + try + { + listener.notify(_layer, _appContext); + } + catch (Throwable t) + { + LOG.warn("Error from RegistrationListener", t); + } + } + } + + public boolean removeListener(RegistrationListener listener) + { + return _listeners.remove(listener); + } + } +} diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticator.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticator.java index 0a56d6d8471..a63059b206d 100644 --- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticator.java +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticator.java @@ -15,72 +15,119 @@ package org.eclipse.jetty.security.jaspi; import java.io.IOException; import java.security.Principal; +import java.util.HashMap; import java.util.Map; import java.util.Set; import javax.security.auth.Subject; -import javax.security.auth.message.AuthException; -import javax.security.auth.message.AuthStatus; -import javax.security.auth.message.callback.CallerPrincipalCallback; -import javax.security.auth.message.callback.GroupPrincipalCallback; -import javax.security.auth.message.config.ServerAuthConfig; -import javax.security.auth.message.config.ServerAuthContext; +import jakarta.security.auth.message.AuthException; +import jakarta.security.auth.message.AuthStatus; +import jakarta.security.auth.message.callback.CallerPrincipalCallback; +import jakarta.security.auth.message.callback.GroupPrincipalCallback; +import jakarta.security.auth.message.config.AuthConfigFactory; +import jakarta.security.auth.message.config.AuthConfigProvider; +import jakarta.security.auth.message.config.RegistrationListener; +import jakarta.security.auth.message.config.ServerAuthConfig; +import jakarta.security.auth.message.config.ServerAuthContext; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; +import org.eclipse.jetty.security.EmptyLoginService; import org.eclipse.jetty.security.IdentityService; +import org.eclipse.jetty.security.LoginService; import org.eclipse.jetty.security.ServerAuthException; import org.eclipse.jetty.security.UserAuthentication; +import org.eclipse.jetty.security.WrappedAuthConfiguration; import org.eclipse.jetty.security.authentication.DeferredAuthentication; import org.eclipse.jetty.security.authentication.LoginAuthenticator; import org.eclipse.jetty.security.authentication.SessionAuthentication; import org.eclipse.jetty.server.Authentication; import org.eclipse.jetty.server.Authentication.User; import org.eclipse.jetty.server.UserIdentity; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + +import static org.eclipse.jetty.security.jaspi.JaspiAuthenticatorFactory.MESSAGE_LAYER; /** - * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $ + * Implementation of Jetty {@link LoginAuthenticator} that is a bridge from Jakarta Authentication to Jetty Security. */ +@SuppressWarnings({"rawtypes", "unchecked"}) public class JaspiAuthenticator extends LoginAuthenticator { - private static final Logger LOG = LoggerFactory.getLogger(JaspiAuthenticator.class.getName()); - - private final ServerAuthConfig _authConfig; - - private final Map _authProperties; - - private final ServletCallbackHandler _callbackHandler; - private final Subject _serviceSubject; - + private final String _appContext; private final boolean _allowLazyAuthentication; + private final AuthConfigFactory _authConfigFactory = AuthConfigFactory.getFactory(); + private Map _authProperties; + private IdentityService _identityService; + private ServletCallbackHandler _callbackHandler; + private ServerAuthConfig _authConfig; - private final IdentityService _identityService; - - public JaspiAuthenticator(ServerAuthConfig authConfig, Map authProperties, ServletCallbackHandler callbackHandler, Subject serviceSubject, - boolean allowLazyAuthentication, IdentityService identityService) + public JaspiAuthenticator(Subject serviceSubject, String appContext, boolean allowLazyAuthentication) + { + _serviceSubject = serviceSubject; + _appContext = appContext; + _allowLazyAuthentication = allowLazyAuthentication; + } + + @Deprecated + public JaspiAuthenticator(ServerAuthConfig authConfig, Map authProperties, ServletCallbackHandler callbackHandler, Subject serviceSubject, boolean allowLazyAuthentication, IdentityService identityService) { - // TODO maybe pass this in via setConfiguration ? if (callbackHandler == null) throw new NullPointerException("No CallbackHandler"); if (authConfig == null) throw new NullPointerException("No AuthConfig"); - this._authConfig = authConfig; this._authProperties = authProperties; this._callbackHandler = callbackHandler; this._serviceSubject = serviceSubject; this._allowLazyAuthentication = allowLazyAuthentication; this._identityService = identityService; + this._appContext = null; + this._authConfig = authConfig; } @Override public void setConfiguration(AuthConfiguration configuration) { + LoginService loginService = configuration.getLoginService(); + if (loginService == null) + { + // Add an empty login service so we can use JASPI without tying into Jetty auth mechanisms. + configuration = new JaspiAuthConfiguration(configuration); + loginService = configuration.getLoginService(); + } + super.setConfiguration(configuration); + + // Only do this if the new constructor was used. + if (_authConfig == null) + { + _identityService = configuration.getIdentityService(); + _callbackHandler = new ServletCallbackHandler(loginService); + _authProperties = new HashMap(); + for (String key : configuration.getInitParameterNames()) + { + _authProperties.put(key, configuration.getInitParameter(key)); + } + } + } + + private ServerAuthConfig getAuthConfig() throws AuthException + { + if (_authConfig != null) + return _authConfig; + + RegistrationListener listener = (layer, appContext) -> _authConfig = null; + AuthConfigProvider authConfigProvider = _authConfigFactory.getConfigProvider(MESSAGE_LAYER, _appContext, listener); + if (authConfigProvider == null) + { + _authConfigFactory.detachListener(listener, MESSAGE_LAYER, _appContext); + return null; + } + + _authConfig = authConfigProvider.getServerAuthConfig(MESSAGE_LAYER, _appContext, _callbackHandler); + return _authConfig; } @Override @@ -124,8 +171,12 @@ public class JaspiAuthenticator extends LoginAuthenticator { try { - String authContextId = _authConfig.getAuthContextID(messageInfo); - ServerAuthContext authContext = _authConfig.getAuthContext(authContextId, _serviceSubject, _authProperties); + ServerAuthConfig authConfig = getAuthConfig(); + if (authConfig == null) + throw new ServerAuthException("No ServerAuthConfig"); + + String authContextId = authConfig.getAuthContextID(messageInfo); + ServerAuthContext authContext = authConfig.getAuthContext(authContextId, _serviceSubject, _authProperties); Subject clientSubject = new Subject(); AuthStatus authStatus = authContext.validateRequest(messageInfo, clientSubject, _serviceSubject); @@ -154,6 +205,8 @@ public class JaspiAuthenticator extends LoginAuthenticator if (principal == null) { String principalName = principalCallback.getName(); + + // TODO: if the Principal class is provided it doesn't need to be in subject, why do we enforce this here? Set principals = principalCallback.getSubject().getPrincipals(); for (Principal p : principals) { @@ -214,8 +267,12 @@ public class JaspiAuthenticator extends LoginAuthenticator { try { - String authContextId = _authConfig.getAuthContextID(messageInfo); - ServerAuthContext authContext = _authConfig.getAuthContext(authContextId, _serviceSubject, _authProperties); + ServerAuthConfig authConfig = getAuthConfig(); + if (authConfig == null) + throw new NullPointerException("no ServerAuthConfig found for context"); + + String authContextId = authConfig.getAuthContextID(messageInfo); + ServerAuthContext authContext = authConfig.getAuthContext(authContextId, _serviceSubject, _authProperties); // TODO // authContext.cleanSubject(messageInfo,validatedUser.getUserIdentity().getSubject()); AuthStatus status = authContext.secureResponse(messageInfo, _serviceSubject); @@ -226,4 +283,20 @@ public class JaspiAuthenticator extends LoginAuthenticator throw new ServerAuthException(e); } } + + private static class JaspiAuthConfiguration extends WrappedAuthConfiguration + { + private final LoginService loginService = new EmptyLoginService(); + + public JaspiAuthConfiguration(AuthConfiguration configuration) + { + super(configuration); + } + + @Override + public LoginService getLoginService() + { + return loginService; + } + } } diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticatorFactory.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticatorFactory.java index 145ab1ce9b8..33554250ee5 100644 --- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticatorFactory.java +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticatorFactory.java @@ -14,17 +14,11 @@ package org.eclipse.jetty.security.jaspi; import java.security.Principal; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Set; import javax.security.auth.Subject; -import javax.security.auth.message.AuthException; -import javax.security.auth.message.config.AuthConfigFactory; -import javax.security.auth.message.config.AuthConfigProvider; -import javax.security.auth.message.config.RegistrationListener; -import javax.security.auth.message.config.ServerAuthConfig; +import jakarta.security.auth.message.config.AuthConfigFactory; import jakarta.servlet.ServletContext; import org.eclipse.jetty.security.Authenticator; import org.eclipse.jetty.security.Authenticator.AuthConfiguration; @@ -32,14 +26,29 @@ import org.eclipse.jetty.security.DefaultAuthenticatorFactory; import org.eclipse.jetty.security.IdentityService; import org.eclipse.jetty.security.LoginService; import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.StringUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * Jakarta Authentication (JASPI) Authenticator Factory. + * + * This is used to link a jetty-security {@link Authenticator.Factory} to a Jakarta Authentication {@link AuthConfigFactory}. + *

+ * This should be initialized with the provided {@link DefaultAuthConfigFactory} to set up Jakarta Authentication {@link AuthConfigFactory} before use. + * (A different {@link AuthConfigFactory} may also be provided using the same steps below) + *

+ * To initialize either: + *

    + *
  • invoke {@link AuthConfigFactory#setFactory(AuthConfigFactory)}
  • + *
  • Alternatively: set {@link AuthConfigFactory#DEFAULT_FACTORY_SECURITY_PROPERTY}
  • + *
+ * + */ public class JaspiAuthenticatorFactory extends DefaultAuthenticatorFactory { private static final Logger LOG = LoggerFactory.getLogger(JaspiAuthenticatorFactory.class); - - private static String MESSAGE_LAYER = "HTTP"; + public static final String MESSAGE_LAYER = "HttpServlet"; private Subject _serviceSubject; private String _serverName; @@ -79,52 +88,26 @@ public class JaspiAuthenticatorFactory extends DefaultAuthenticatorFactory @Override public Authenticator getAuthenticator(Server server, ServletContext context, AuthConfiguration configuration, IdentityService identityService, LoginService loginService) { - Authenticator authenticator = null; - try - { - AuthConfigFactory authConfigFactory = AuthConfigFactory.getFactory(); - RegistrationListener listener = (layer, appContext) -> - { - }; + AuthConfigFactory factory = AuthConfigFactory.getFactory(); + if (factory == null) + return null; - Subject serviceSubject = findServiceSubject(server); - String serverName = findServerName(server); + String serverName = findServerName(context, server); + Subject serviceSubject = findServiceSubject(server); + String contextPath = StringUtil.isEmpty(context.getContextPath()) ? "/" : context.getContextPath(); + String appContext = serverName + " " + contextPath; - String contextPath = context.getContextPath(); - if (contextPath == null || contextPath.length() == 0) - contextPath = "/"; - String appContext = serverName + " " + contextPath; + // We will only create the Authenticator if an AuthConfigProvider matches this context. + if (factory.getConfigProvider(MESSAGE_LAYER, appContext, null) == null) + return null; - AuthConfigProvider authConfigProvider = authConfigFactory.getConfigProvider(MESSAGE_LAYER, appContext, listener); - - if (authConfigProvider != null) - { - ServletCallbackHandler servletCallbackHandler = new ServletCallbackHandler(loginService); - ServerAuthConfig serverAuthConfig = authConfigProvider.getServerAuthConfig(MESSAGE_LAYER, appContext, servletCallbackHandler); - if (serverAuthConfig != null) - { - Map map = new HashMap(); - for (String key : configuration.getInitParameterNames()) - { - map.put(key, configuration.getInitParameter(key)); - } - authenticator = new JaspiAuthenticator(serverAuthConfig, map, servletCallbackHandler, - serviceSubject, true, identityService); - } - } - } - catch (AuthException e) - { - LOG.warn("Failed to get ServerAuthConfig", e); - } - return authenticator; + return new JaspiAuthenticator(serviceSubject, appContext, true); } /** - * Find a service Subject. - * If {@link #setServiceSubject(Subject)} has not been used to - * set a subject, then the {@link Server#getBeans(Class)} method is - * used to look for a Subject. + * Find a service Subject. If {@link #setServiceSubject(Subject)} has not been + * used to set a subject, then the {@link Server#getBeans(Class)} method is used + * to look for a Subject. * * @param server the server to pull the Subject from * @return the subject @@ -140,18 +123,24 @@ public class JaspiAuthenticatorFactory extends DefaultAuthenticatorFactory } /** - * Find a servername. - * If {@link #setServerName(String)} has not been called, then - * use the name of the a principal in the service subject. - * If not found, return "server". + * Find a servername. If {@link #setServerName(String)} has not been called, + * then use the virtualServerName of the context. + * If this is also null, then use the name of the a principal in the service subject. + * If none are found, return "server". + * @param context * * @param server the server to find the name of - * @return the server name from the service Subject (or default value if not found in subject or principals) + * @return the server name from the service Subject (or default value if not + * found in subject or principals) */ - protected String findServerName(Server server) - { + protected String findServerName(ServletContext context, Server server) + { if (_serverName != null) return _serverName; + + String virtualServerName = context.getVirtualServerName(); + if (virtualServerName != null) + return virtualServerName; Subject subject = findServiceSubject(server); if (subject != null) diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiMessageInfo.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiMessageInfo.java index aa8bbc8b141..0b9413b610f 100644 --- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiMessageInfo.java +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiMessageInfo.java @@ -17,8 +17,8 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; -import javax.security.auth.message.MessageInfo; +import jakarta.security.auth.message.MessageInfo; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; @@ -27,7 +27,7 @@ import jakarta.servlet.ServletResponse; */ public class JaspiMessageInfo implements MessageInfo { - public static final String MANDATORY_KEY = "javax.security.auth.message.MessagePolicy.isMandatory"; + public static final String MANDATORY_KEY = "jakarta.security.auth.message.MessagePolicy.isMandatory"; public static final String AUTH_METHOD_KEY = "jakarta.servlet.http.authType"; private ServletRequest request; private ServletResponse response; diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/ServletCallbackHandler.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/ServletCallbackHandler.java index 0eb60389f16..fc80fc53337 100644 --- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/ServletCallbackHandler.java +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/ServletCallbackHandler.java @@ -18,14 +18,14 @@ import javax.security.auth.Subject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.UnsupportedCallbackException; -import javax.security.auth.message.callback.CallerPrincipalCallback; -import javax.security.auth.message.callback.CertStoreCallback; -import javax.security.auth.message.callback.GroupPrincipalCallback; -import javax.security.auth.message.callback.PasswordValidationCallback; -import javax.security.auth.message.callback.PrivateKeyCallback; -import javax.security.auth.message.callback.SecretKeyCallback; -import javax.security.auth.message.callback.TrustStoreCallback; +import jakarta.security.auth.message.callback.CallerPrincipalCallback; +import jakarta.security.auth.message.callback.CertStoreCallback; +import jakarta.security.auth.message.callback.GroupPrincipalCallback; +import jakarta.security.auth.message.callback.PasswordValidationCallback; +import jakarta.security.auth.message.callback.PrivateKeyCallback; +import jakarta.security.auth.message.callback.SecretKeyCallback; +import jakarta.security.auth.message.callback.TrustStoreCallback; import org.eclipse.jetty.security.LoginService; import org.eclipse.jetty.security.authentication.LoginCallback; import org.eclipse.jetty.security.authentication.LoginCallbackImpl; @@ -33,14 +33,13 @@ import org.eclipse.jetty.security.jaspi.callback.CredentialValidationCallback; import org.eclipse.jetty.server.UserIdentity; /** - * Idiot class required by jaspi stupidity + * This {@link CallbackHandler} will bridge {@link Callback}s to handle to the given to the Jetty {@link LoginService}. */ public class ServletCallbackHandler implements CallbackHandler { private final LoginService _loginService; - - private final ThreadLocal _callerPrincipals = new ThreadLocal(); - private final ThreadLocal _groupPrincipals = new ThreadLocal(); + private final ThreadLocal _callerPrincipals = new ThreadLocal<>(); + private final ThreadLocal _groupPrincipals = new ThreadLocal<>(); public ServletCallbackHandler(LoginService loginService) { @@ -64,6 +63,7 @@ public class ServletCallbackHandler implements CallbackHandler else if (callback instanceof PasswordValidationCallback) { PasswordValidationCallback passwordValidationCallback = (PasswordValidationCallback)callback; + @SuppressWarnings("unused") Subject subject = passwordValidationCallback.getSubject(); UserIdentity user = _loginService.login(passwordValidationCallback.getUsername(), passwordValidationCallback.getPassword(), null); diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/SimpleAuthConfig.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/SimpleAuthConfig.java index c2666943ab8..a6f75194926 100644 --- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/SimpleAuthConfig.java +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/SimpleAuthConfig.java @@ -15,11 +15,15 @@ package org.eclipse.jetty.security.jaspi; import java.util.Map; import javax.security.auth.Subject; -import javax.security.auth.message.AuthException; -import javax.security.auth.message.MessageInfo; -import javax.security.auth.message.config.ServerAuthConfig; -import javax.security.auth.message.config.ServerAuthContext; +import jakarta.security.auth.message.MessageInfo; +import jakarta.security.auth.message.config.ServerAuthConfig; +import jakarta.security.auth.message.config.ServerAuthContext; + +/** + * @deprecated use {@link org.eclipse.jetty.security.jaspi.provider.JaspiAuthConfigProvider}. + */ +@Deprecated public class SimpleAuthConfig implements ServerAuthConfig { public static final String HTTP_SERVLET = "HttpServlet"; @@ -35,7 +39,7 @@ public class SimpleAuthConfig implements ServerAuthConfig } @Override - public ServerAuthContext getAuthContext(String authContextID, Subject serviceSubject, Map properties) throws AuthException + public ServerAuthContext getAuthContext(String authContextID, Subject serviceSubject, Map properties) { return _serverAuthContext; } diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BaseAuthModule.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BaseAuthModule.java index 8057c40e8d8..f608b3d137c 100644 --- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BaseAuthModule.java +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BaseAuthModule.java @@ -22,15 +22,15 @@ import javax.security.auth.Subject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.UnsupportedCallbackException; -import javax.security.auth.message.AuthException; -import javax.security.auth.message.AuthStatus; -import javax.security.auth.message.MessageInfo; -import javax.security.auth.message.MessagePolicy; -import javax.security.auth.message.callback.CallerPrincipalCallback; -import javax.security.auth.message.callback.GroupPrincipalCallback; -import javax.security.auth.message.config.ServerAuthContext; -import javax.security.auth.message.module.ServerAuthModule; +import jakarta.security.auth.message.AuthException; +import jakarta.security.auth.message.AuthStatus; +import jakarta.security.auth.message.MessageInfo; +import jakarta.security.auth.message.MessagePolicy; +import jakarta.security.auth.message.callback.CallerPrincipalCallback; +import jakarta.security.auth.message.callback.GroupPrincipalCallback; +import jakarta.security.auth.message.config.ServerAuthContext; +import jakarta.security.auth.message.module.ServerAuthModule; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.security.authentication.LoginCallbackImpl; @@ -39,7 +39,11 @@ import org.eclipse.jetty.security.jaspi.callback.CredentialValidationCallback; import org.eclipse.jetty.util.security.Credential; import org.eclipse.jetty.util.security.Password; -public class BaseAuthModule implements ServerAuthModule, ServerAuthContext +/** + * Simple abstract module implementing a Jakarta Authentication {@link ServerAuthModule} and {@link ServerAuthContext}. + * To be used as a building block for building more sophisticated auth modules. + */ +public abstract class BaseAuthModule implements ServerAuthModule, ServerAuthContext { private static final Class[] SUPPORTED_MESSAGE_TYPES = new Class[]{HttpServletRequest.class, HttpServletResponse.class}; @@ -92,12 +96,6 @@ public class BaseAuthModule implements ServerAuthModule, ServerAuthContext return AuthStatus.SEND_SUCCESS; } - @Override - public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject) throws AuthException - { - return AuthStatus.SEND_FAILURE; - } - /** * @param messageInfo message info to examine for mandatory flag * @return whether authentication is mandatory or optional @@ -110,9 +108,7 @@ public class BaseAuthModule implements ServerAuthModule, ServerAuthContext return Boolean.parseBoolean(mandatory); } - protected boolean login(Subject clientSubject, String credentials, - String authMethod, MessageInfo messageInfo) - throws IOException, UnsupportedCallbackException + protected boolean login(Subject clientSubject, String credentials, String authMethod, MessageInfo messageInfo) throws IOException, UnsupportedCallbackException { credentials = credentials.substring(credentials.indexOf(' ') + 1); credentials = new String(Base64.getDecoder().decode(credentials), StandardCharsets.ISO_8859_1); @@ -122,10 +118,7 @@ public class BaseAuthModule implements ServerAuthModule, ServerAuthContext return login(clientSubject, userName, new Password(password), authMethod, messageInfo); } - protected boolean login(Subject clientSubject, String username, - Credential credential, String authMethod, - MessageInfo messageInfo) - throws IOException, UnsupportedCallbackException + protected boolean login(Subject clientSubject, String username, Credential credential, String authMethod, MessageInfo messageInfo) throws IOException, UnsupportedCallbackException { CredentialValidationCallback credValidationCallback = new CredentialValidationCallback(clientSubject, username, credential); callbackHandler.handle(new Callback[]{credValidationCallback}); diff --git a/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/BasicAuthModule.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BasicAuthenticationAuthModule.java similarity index 73% rename from jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/BasicAuthModule.java rename to jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BasicAuthenticationAuthModule.java index 4b3f8cb0c45..cbe2191c673 100644 --- a/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/BasicAuthModule.java +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BasicAuthenticationAuthModule.java @@ -11,57 +11,56 @@ // ======================================================================== // -package org.eclipse.jetty.security.jaspi; +package org.eclipse.jetty.security.jaspi.modules; import java.io.IOException; import java.util.Map; import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.UnsupportedCallbackException; -import javax.security.auth.message.AuthException; -import javax.security.auth.message.AuthStatus; -import javax.security.auth.message.MessageInfo; -import javax.security.auth.message.MessagePolicy; +import jakarta.security.auth.message.AuthException; +import jakarta.security.auth.message.AuthStatus; +import jakarta.security.auth.message.MessageInfo; +import jakarta.security.auth.message.MessagePolicy; +import jakarta.security.auth.message.module.ServerAuthModule; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.security.jaspi.modules.BaseAuthModule; import org.eclipse.jetty.util.security.Constraint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class BasicAuthModule extends BaseAuthModule +/** + * A {@link ServerAuthModule} implementation of HTTP Basic Authentication. + */ +public class BasicAuthenticationAuthModule extends BaseAuthModule { - private static final Logger LOG = LoggerFactory.getLogger(BasicAuthModule.class); + private static final Logger LOG = LoggerFactory.getLogger(BasicAuthenticationAuthModule.class); private String realmName; private static final String REALM_KEY = "org.eclipse.jetty.security.jaspi.modules.RealmName"; - public BasicAuthModule() + public BasicAuthenticationAuthModule() { } - public BasicAuthModule(CallbackHandler callbackHandler, String realmName) + public BasicAuthenticationAuthModule(CallbackHandler callbackHandler, String realmName) { super(callbackHandler); this.realmName = realmName; } @Override - public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy, - CallbackHandler handler, Map options) - throws AuthException + public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy, CallbackHandler callbackHandler, Map options) throws AuthException { - super.initialize(requestPolicy, responsePolicy, handler, options); + super.initialize(requestPolicy, responsePolicy, callbackHandler, options); realmName = (String)options.get(REALM_KEY); } @Override - public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, - Subject serviceSubject) - throws AuthException + public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject) throws AuthException { HttpServletRequest request = (HttpServletRequest)messageInfo.getRequestMessage(); HttpServletResponse response = (HttpServletResponse)messageInfo.getResponseMessage(); @@ -87,11 +86,7 @@ public class BasicAuthModule extends BaseAuthModule response.sendError(HttpServletResponse.SC_UNAUTHORIZED); return AuthStatus.SEND_CONTINUE; } - catch (IOException e) - { - throw new AuthException(e.getMessage()); - } - catch (UnsupportedCallbackException e) + catch (IOException | UnsupportedCallbackException e) { throw new AuthException(e.getMessage()); } diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/UserInfo.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/UserInfo.java deleted file mode 100644 index 993389a48f9..00000000000 --- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/UserInfo.java +++ /dev/null @@ -1,45 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.security.jaspi.modules; - -import java.util.Arrays; - -public class UserInfo -{ - private final String userName; - - private char[] password; - - public UserInfo(String userName, char[] password) - { - this.userName = userName; - this.password = password; - } - - public String getUserName() - { - return userName; - } - - public char[] getPassword() - { - return password; - } - - public void clearPassword() - { - Arrays.fill(password, (char)0); - password = null; - } -} diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/provider/JaspiAuthConfigProvider.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/provider/JaspiAuthConfigProvider.java new file mode 100644 index 00000000000..581575df945 --- /dev/null +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/provider/JaspiAuthConfigProvider.java @@ -0,0 +1,132 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.security.jaspi.provider; + +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import javax.security.auth.callback.CallbackHandler; + +import jakarta.security.auth.message.config.AuthConfigFactory; +import jakarta.security.auth.message.config.AuthConfigProvider; +import jakarta.security.auth.message.config.ClientAuthConfig; +import jakarta.security.auth.message.config.ServerAuthConfig; +import jakarta.security.auth.message.module.ServerAuthModule; +import org.eclipse.jetty.security.jaspi.JaspiAuthenticatorFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + *

A Jetty implementation of the {@link AuthConfigProvider} to allow registration of a {@link ServerAuthModule} + * directly without having to write a custom {@link AuthConfigProvider}.

+ *

If this is being constructed by an {@link AuthConfigFactory} after being passed in as a className, then + * you will need to provide the property {@code ServerAuthModule} containing the fully qualified name of + * the {@link ServerAuthModule} class you wish to use.

+ */ +@SuppressWarnings("rawtypes") +public class JaspiAuthConfigProvider implements AuthConfigProvider +{ + private static final Logger LOG = LoggerFactory.getLogger(JaspiAuthConfigProvider.class); + private final Map providerProperties; + private final ServerAuthModule serverAuthModule; + + /** + *

Constructor with signature and implementation that's required by API.

+ *

The property map must contain the {@code ServerAuthModule} property containing the fully qualified name of + * the {@link ServerAuthModule} class you wish to use. If this constructor is being used for self-registration an + * optional property of {@code appContext} can be used specify the appContext value to register the provider.

+ * + * @param properties A Map of initialization properties. + * @param factory The {@link AuthConfigFactory} to register on. + */ + public JaspiAuthConfigProvider(Map properties, AuthConfigFactory factory) + { + if (properties == null || !properties.containsKey("ServerAuthModule")) + throw new IllegalArgumentException("Missing property 'ServerAuthModule', cannot create JaspiAuthConfigProvider"); + + this.providerProperties = Map.copyOf(properties); + this.serverAuthModule = createServerAuthModule((String)properties.get("ServerAuthModule")); + + // API requires self registration if factory is provided. + if (factory != null) + factory.registerConfigProvider(this, JaspiAuthenticatorFactory.MESSAGE_LAYER, (String)properties.get("appContext"), "Self Registration"); + } + + /** + * @param className The fully qualified name of a {@link ServerAuthModule} class. + */ + public JaspiAuthConfigProvider(String className) + { + this(className, null); + } + + /** + * @param className The fully qualified name of a {@link ServerAuthModule} class. + * @param properties A Map of initialization properties. + */ + public JaspiAuthConfigProvider(String className, Map properties) + { + this(createServerAuthModule(className), properties); + } + + /** + * @param serverAuthModule The instance of {@link ServerAuthModule} to use. + */ + public JaspiAuthConfigProvider(ServerAuthModule serverAuthModule) + { + this.serverAuthModule = Objects.requireNonNull(serverAuthModule); + this.providerProperties = Collections.emptyMap(); + } + + /** + * @param serverAuthModule The instance of {@link ServerAuthModule} to use. + * @param properties A Map of initialization properties. + */ + public JaspiAuthConfigProvider(ServerAuthModule serverAuthModule, Map properties) + { + this.serverAuthModule = Objects.requireNonNull(serverAuthModule); + this.providerProperties = properties == null ? Collections.emptyMap() : Map.copyOf(properties); + } + + @Override + public ClientAuthConfig getClientAuthConfig(String layer, String appContext, CallbackHandler handler) + { + return null; + } + + @Override + public ServerAuthConfig getServerAuthConfig(String layer, String appContext, CallbackHandler handler) + { + if (LOG.isDebugEnabled()) + LOG.debug("getServerAuthConfig"); + return new SimpleAuthConfig(layer, appContext, handler, providerProperties, serverAuthModule); + } + + @Override + public void refresh() + { + } + + private static ServerAuthModule createServerAuthModule(String serverAuthModuleClassName) + { + try + { + return (ServerAuthModule)Class.forName(serverAuthModuleClassName).getDeclaredConstructor().newInstance(); + } + catch (ReflectiveOperationException e) + { + throw new IllegalStateException(e); + } + } +} \ No newline at end of file diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/provider/SimpleAuthConfig.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/provider/SimpleAuthConfig.java new file mode 100644 index 00000000000..92264535080 --- /dev/null +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/provider/SimpleAuthConfig.java @@ -0,0 +1,84 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.security.jaspi.provider; + +import java.util.Map; +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; + +import jakarta.security.auth.message.AuthException; +import jakarta.security.auth.message.MessageInfo; +import jakarta.security.auth.message.config.ServerAuthConfig; +import jakarta.security.auth.message.config.ServerAuthContext; +import jakarta.security.auth.message.module.ServerAuthModule; + +/** + * Simple implementation of the {@link ServerAuthConfig} interface. + * + * This implementation wires up the given {@link ServerAuthModule} to the appropriate Jakarta Authentication {@link ServerAuthContext} responsible + * for providing it. + */ +@SuppressWarnings("rawtypes") +class SimpleAuthConfig implements ServerAuthConfig +{ + private final String _messageLayer; + private final String _appContext; + private final CallbackHandler _callbackHandler; + private final Map _properties; + private final ServerAuthModule _serverAuthModule; + + public SimpleAuthConfig(String messageLayer, String appContext, CallbackHandler callbackHandler, Map properties, ServerAuthModule serverAuthModule) + { + _messageLayer = messageLayer; + _appContext = appContext; + _callbackHandler = callbackHandler; + _properties = properties; + _serverAuthModule = serverAuthModule; + } + + @Override + public ServerAuthContext getAuthContext(String authContextID, Subject serviceSubject, Map properties) throws AuthException + { + return new SimpleServerAuthContext(_callbackHandler, _serverAuthModule, _properties); + } + + @Override + public String getAppContext() + { + return _appContext; + } + + @Override + public String getAuthContextID(MessageInfo messageInfo) + { + return null; + } + + @Override + public String getMessageLayer() + { + return _messageLayer; + } + + @Override + public boolean isProtected() + { + return true; + } + + @Override + public void refresh() + { + } +} \ No newline at end of file diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/provider/SimpleServerAuthContext.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/provider/SimpleServerAuthContext.java new file mode 100644 index 00000000000..9ce7e93b229 --- /dev/null +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/provider/SimpleServerAuthContext.java @@ -0,0 +1,59 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.security.jaspi.provider; + +import java.util.Map; +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; + +import jakarta.security.auth.message.AuthException; +import jakarta.security.auth.message.AuthStatus; +import jakarta.security.auth.message.MessageInfo; +import jakarta.security.auth.message.config.ServerAuthContext; +import jakarta.security.auth.message.module.ServerAuthModule; + +/** + * Simple bridge implementation of the Jakarta Authentication {@link ServerAuthContext} interface. + * + * This implementation will only delegate to the provided {@link ServerAuthModule} implementation. + */ +class SimpleServerAuthContext implements ServerAuthContext +{ + private final ServerAuthModule serverAuthModule; + + @SuppressWarnings("rawtypes") + public SimpleServerAuthContext(CallbackHandler callbackHandler, ServerAuthModule serverAuthModule, Map properties) throws AuthException + { + this.serverAuthModule = serverAuthModule; + serverAuthModule.initialize(null, null, callbackHandler, properties); + } + + @Override + public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject) throws AuthException + { + return serverAuthModule.validateRequest(messageInfo, clientSubject, serviceSubject); + } + + @Override + public AuthStatus secureResponse(MessageInfo messageInfo, Subject serviceSubject) throws AuthException + { + return serverAuthModule.secureResponse(messageInfo, serviceSubject); + } + + @Override + public void cleanSubject(MessageInfo messageInfo, Subject subject) throws AuthException + { + serverAuthModule.cleanSubject(messageInfo, subject); + } +} \ No newline at end of file diff --git a/jetty-jaspi/src/main/resources/META-INF/services/org.eclipse.jetty.security.Authenticator$Factory b/jetty-jaspi/src/main/resources/META-INF/services/org.eclipse.jetty.security.Authenticator$Factory index 2e24fa8ca2a..03cf3bbc1a2 100644 --- a/jetty-jaspi/src/main/resources/META-INF/services/org.eclipse.jetty.security.Authenticator$Factory +++ b/jetty-jaspi/src/main/resources/META-INF/services/org.eclipse.jetty.security.Authenticator$Factory @@ -1 +1 @@ -org.eclipse.jetty.security.jaspi.JaspiAuthenticatorFactory \ No newline at end of file +org.eclipse.jetty.security.jaspi.JaspiAuthenticatorFactory diff --git a/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/DefaultAuthConfigFactoryTest.java b/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/DefaultAuthConfigFactoryTest.java new file mode 100644 index 00000000000..a3c2809aa59 --- /dev/null +++ b/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/DefaultAuthConfigFactoryTest.java @@ -0,0 +1,115 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.security.jaspi; + +import java.util.Map; + +import jakarta.security.auth.message.config.AuthConfigFactory; +import jakarta.security.auth.message.config.AuthConfigProvider; +import jakarta.security.auth.message.config.RegistrationListener; +import org.eclipse.jetty.security.jaspi.provider.JaspiAuthConfigProvider; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.arrayContaining; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; + +public class DefaultAuthConfigFactoryTest +{ + + private static final String MESSAGE_LAYER = "HttpServlet"; + + private final String jettyAuthConfigProvider = "org.eclipse.jetty.security.jaspi.provider.JaspiAuthConfigProvider"; + private final String appContext = "server /test"; + + private final Map serverAuthModuleProperties = Map.of("ServerAuthModule", + "org.eclipse.jetty.security.jaspi.modules.BasicAuthenticationAuthModule", "AppContextID", appContext, + "org.eclipse.jetty.security.jaspi.modules.RealmName", "TestRealm"); + + private final String serverAuthModuleClassName = "org.eclipse.jetty.security.jaspi.modules.BasicAuthenticationAuthModule"; + + @Test + public void testRegisterConfigProviderByClassName() throws Exception + { + AuthConfigFactory factory = new DefaultAuthConfigFactory(); + String registrationId = factory.registerConfigProvider(jettyAuthConfigProvider, + serverAuthModuleProperties, MESSAGE_LAYER, appContext, "a test provider"); + AuthConfigProvider registeredProvider = factory.getConfigProvider(MESSAGE_LAYER, appContext, null); + assertThat(registeredProvider, instanceOf(JaspiAuthConfigProvider.class)); + assertThat(registeredProvider.getServerAuthConfig(MESSAGE_LAYER, appContext, null), notNullValue()); + + assertThat(factory.getRegistrationContext(registrationId), notNullValue()); + assertThat(factory.getRegistrationIDs(registeredProvider), arrayContaining(registrationId)); + } + + @Test + public void testRegisterAuthConfigProviderDirect() throws Exception + { + AuthConfigProvider provider = new JaspiAuthConfigProvider( + serverAuthModuleClassName, + serverAuthModuleProperties); + + AuthConfigFactory factory = new DefaultAuthConfigFactory(); + String registrationId = factory.registerConfigProvider(provider, MESSAGE_LAYER, appContext, "a test provider"); + + AuthConfigProvider registeredProvider = factory.getConfigProvider(MESSAGE_LAYER, appContext, null); + assertThat(registeredProvider, instanceOf(JaspiAuthConfigProvider.class)); + assertThat(registeredProvider.getServerAuthConfig(MESSAGE_LAYER, appContext, null), notNullValue()); + + assertThat(factory.getRegistrationContext(registrationId), notNullValue()); + assertThat(factory.getRegistrationIDs(registeredProvider), arrayContaining(registrationId)); + } + + @Test + public void testRemoveRegistration() throws Exception + { + // Arrange + AuthConfigProvider provider = new JaspiAuthConfigProvider( + serverAuthModuleClassName, + serverAuthModuleProperties); + + AuthConfigFactory factory = new DefaultAuthConfigFactory(); + String registrationId = factory.registerConfigProvider(provider, MESSAGE_LAYER, appContext, "a test provider"); + + DummyRegistrationListener dummyListener = new DummyRegistrationListener(); + assertThat(factory.getConfigProvider(MESSAGE_LAYER, appContext, dummyListener), notNullValue()); + + // Act + factory.removeRegistration(registrationId); + + // Assert config provider removed + assertThat(factory.getConfigProvider(MESSAGE_LAYER, appContext, null), nullValue()); + + // Assert listeners invoked + assertThat(dummyListener.appContext, equalTo(appContext)); + assertThat(dummyListener.layer, equalTo(MESSAGE_LAYER)); + + } + + static class DummyRegistrationListener implements RegistrationListener + { + String layer; + String appContext; + + @Override + public void notify(String layer, String appContext) + { + this.layer = layer; + this.appContext = appContext; + } + } +} diff --git a/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/HttpHeaderAuthModule.java b/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/HttpHeaderAuthModule.java index 9720117e5e6..6d9b5100baf 100644 --- a/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/HttpHeaderAuthModule.java +++ b/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/HttpHeaderAuthModule.java @@ -17,14 +17,14 @@ import java.util.Map; import javax.security.auth.Subject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.message.AuthException; -import javax.security.auth.message.AuthStatus; -import javax.security.auth.message.MessageInfo; -import javax.security.auth.message.MessagePolicy; -import javax.security.auth.message.callback.CallerPrincipalCallback; -import javax.security.auth.message.callback.GroupPrincipalCallback; -import javax.security.auth.message.module.ServerAuthModule; +import jakarta.security.auth.message.AuthException; +import jakarta.security.auth.message.AuthStatus; +import jakarta.security.auth.message.MessageInfo; +import jakarta.security.auth.message.MessagePolicy; +import jakarta.security.auth.message.callback.CallerPrincipalCallback; +import jakarta.security.auth.message.callback.GroupPrincipalCallback; +import jakarta.security.auth.message.module.ServerAuthModule; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; diff --git a/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/JaspiTest.java b/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/JaspiTest.java index 90832fd9fce..6aae1dd3c89 100644 --- a/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/JaspiTest.java +++ b/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/JaspiTest.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import jakarta.security.auth.message.config.AuthConfigFactory; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -39,7 +40,9 @@ import org.eclipse.jetty.util.security.Constraint; import org.eclipse.jetty.util.security.Credential; import org.eclipse.jetty.util.security.Password; import org.hamcrest.Matchers; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -52,7 +55,7 @@ public class JaspiTest Server _server; LocalConnector _connector; - public class TestLoginService extends AbstractLoginService + public static class TestLoginService extends AbstractLoginService { protected Map _users = new HashMap<>(); protected Map> _roles = new HashMap<>(); @@ -86,10 +89,34 @@ public class JaspiTest } } + @BeforeAll + public static void beforeAll() throws Exception + { + AuthConfigFactory factory = new DefaultAuthConfigFactory(); + + factory.registerConfigProvider("org.eclipse.jetty.security.jaspi.provider.JaspiAuthConfigProvider", + Map.of("ServerAuthModule", "org.eclipse.jetty.security.jaspi.modules.BasicAuthenticationAuthModule", + "AppContextID", "server /ctx", + "org.eclipse.jetty.security.jaspi.modules.RealmName", "TestRealm"), + "HttpServlet", "server /ctx", "a test provider"); + + factory.registerConfigProvider("org.eclipse.jetty.security.jaspi.provider.JaspiAuthConfigProvider", + Map.of("ServerAuthModule", "org.eclipse.jetty.security.jaspi.HttpHeaderAuthModule", + "AppContextID", "server /other"), + "HttpServlet", "server /other", "another test provider"); + + AuthConfigFactory.setFactory(factory); + } + + @AfterAll + public static void afterAll() throws Exception + { + AuthConfigFactory.setFactory(null); + } + @BeforeEach public void before() throws Exception { - System.setProperty("org.apache.geronimo.jaspic.configurationFile", "src/test/resources/jaspi.xml"); _server = new Server(); _connector = new LocalConnector(_server); _server.addConnector(_connector); @@ -98,8 +125,8 @@ public class JaspiTest _server.setHandler(contexts); TestLoginService loginService = new TestLoginService("TestRealm"); - loginService.putUser("user", new Password("password"), new String[]{"users"}); - loginService.putUser("admin", new Password("secret"), new String[]{"users", "admins"}); + loginService.putUser("user", new Password("password"), new String[] {"users"}); + loginService.putUser("admin", new Password("secret"), new String[] {"users", "admins"}); _server.addBean(loginService); ContextHandler context = new ContextHandler(); @@ -159,9 +186,8 @@ public class JaspiTest @Test public void testConstraintWrongAuth() throws Exception { - String response = _connector.getResponse("GET /ctx/jaspi/test HTTP/1.0\n" + - "Authorization: Basic " + Base64.getEncoder().encodeToString("user:wrong".getBytes(ISO_8859_1)) + - "\n\n"); + String response = _connector.getResponse("GET /ctx/jaspi/test HTTP/1.0\n" + "Authorization: Basic " + + Base64.getEncoder().encodeToString("user:wrong".getBytes(ISO_8859_1)) + "\n\n"); assertThat(response, startsWith("HTTP/1.1 401 Unauthorized")); assertThat(response, Matchers.containsString("WWW-Authenticate: basic realm=\"TestRealm\"")); } @@ -169,9 +195,8 @@ public class JaspiTest @Test public void testConstraintAuth() throws Exception { - String response = _connector.getResponse("GET /ctx/jaspi/test HTTP/1.0\n" + - "Authorization: Basic " + Base64.getEncoder().encodeToString("user:password".getBytes(ISO_8859_1)) + - "\n\n"); + String response = _connector.getResponse("GET /ctx/jaspi/test HTTP/1.0\n" + "Authorization: Basic " + + Base64.getEncoder().encodeToString("user:password".getBytes(ISO_8859_1)) + "\n\n"); assertThat(response, startsWith("HTTP/1.1 200 OK")); } @@ -185,8 +210,7 @@ public class JaspiTest @Test public void testOtherAuth() throws Exception { - String response = _connector.getResponse("GET /other/test HTTP/1.0\n" + - "X-Forwarded-User: user\n\n"); + String response = _connector.getResponse("GET /other/test HTTP/1.0\n" + "X-Forwarded-User: user\n\n"); assertThat(response, startsWith("HTTP/1.1 200 OK")); } @@ -194,7 +218,8 @@ public class JaspiTest { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException { baseRequest.setHandled(true); response.setStatus(200); diff --git a/jetty-jaspi/src/test/resources/jaspi.xml b/jetty-jaspi/src/test/resources/jaspi.xml deleted file mode 100644 index 1e31d2c996b..00000000000 --- a/jetty-jaspi/src/test/resources/jaspi.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - HTTP - server /ctx - description - - authenticationContextID1 - true - - - org.eclipse.jetty.security.jaspi.BasicAuthModule - - org.eclipse.jetty.security.jaspi.modules.RealmName=TestRealm - - - - - true - - - HTTP - server /other - description - - authenticationContextID2 - true - - - org.eclipse.jetty.security.jaspi.HttpHeaderAuthModule - - - - - - true - - diff --git a/jetty-maven-plugin/pom.xml b/jetty-maven-plugin/pom.xml index 14e4edefa14..9737365b8c2 100644 --- a/jetty-maven-plugin/pom.xml +++ b/jetty-maven-plugin/pom.xml @@ -358,10 +358,6 @@ org.eclipse.jetty.orbit javax.mail.glassfish - - org.eclipse.jetty.orbit - javax.security.auth.message - org.ow2.asm asm-tree diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/EmptyLoginService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/EmptyLoginService.java new file mode 100644 index 00000000000..291341295b2 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/EmptyLoginService.java @@ -0,0 +1,57 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.security; + +import jakarta.servlet.ServletRequest; +import org.eclipse.jetty.server.UserIdentity; + +/** + * LoginService implementation which always denies any attempt to login. + */ +public class EmptyLoginService implements LoginService +{ + @Override + public String getName() + { + return null; + } + + @Override + public UserIdentity login(String username, Object credentials, ServletRequest request) + { + return null; + } + + @Override + public boolean validate(UserIdentity user) + { + return false; + } + + @Override + public IdentityService getIdentityService() + { + return null; + } + + @Override + public void setIdentityService(IdentityService service) + { + } + + @Override + public void logout(UserIdentity user) + { + } +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/WrappedAuthConfiguration.java b/jetty-security/src/main/java/org/eclipse/jetty/security/WrappedAuthConfiguration.java new file mode 100644 index 00000000000..f5c93381e95 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/WrappedAuthConfiguration.java @@ -0,0 +1,74 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.security; + +import java.util.Set; + +import org.eclipse.jetty.security.Authenticator.AuthConfiguration; + +/** + * A wrapper for {@link AuthConfiguration}. This allows you create a new AuthConfiguration which can + * override a method to change a value from an another instance of AuthConfiguration. + */ +public class WrappedAuthConfiguration implements AuthConfiguration +{ + private final AuthConfiguration _configuration; + + public WrappedAuthConfiguration(AuthConfiguration configuration) + { + _configuration = configuration; + } + + @Override + public String getAuthMethod() + { + return _configuration.getAuthMethod(); + } + + @Override + public String getRealmName() + { + return _configuration.getRealmName(); + } + + @Override + public String getInitParameter(String param) + { + return _configuration.getInitParameter(param); + } + + @Override + public Set getInitParameterNames() + { + return _configuration.getInitParameterNames(); + } + + @Override + public LoginService getLoginService() + { + return _configuration.getLoginService(); + } + + @Override + public IdentityService getIdentityService() + { + return _configuration.getIdentityService(); + } + + @Override + public boolean isSessionRenewedOnAuthentication() + { + return _configuration.isSessionRenewedOnAuthentication(); + } +} diff --git a/jetty-webapp/src/main/java/module-info.java b/jetty-webapp/src/main/java/module-info.java index 5da2ec13e74..5121f5eca99 100644 --- a/jetty-webapp/src/main/java/module-info.java +++ b/jetty-webapp/src/main/java/module-info.java @@ -27,6 +27,7 @@ module org.eclipse.jetty.webapp provides Configuration with org.eclipse.jetty.webapp.FragmentConfiguration, org.eclipse.jetty.webapp.JaasConfiguration, + org.eclipse.jetty.webapp.JaspiConfiguration, org.eclipse.jetty.webapp.JettyWebXmlConfiguration, org.eclipse.jetty.webapp.JmxConfiguration, org.eclipse.jetty.webapp.JndiConfiguration, diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JaspiConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JaspiConfiguration.java new file mode 100644 index 00000000000..5f400194c33 --- /dev/null +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JaspiConfiguration.java @@ -0,0 +1,50 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.webapp; + +import org.eclipse.jetty.util.Loader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + *

JASPI Configuration

+ *

This configuration configures the WebAppContext server/system classes to + * not be able to see the {@code jakarta.security.auth.message} package.

+ */ +public class JaspiConfiguration extends AbstractConfiguration +{ + private static final Logger LOG = LoggerFactory.getLogger(JaspiConfiguration.class); + + public JaspiConfiguration() + { + addDependencies(WebXmlConfiguration.class, MetaInfConfiguration.class, WebInfConfiguration.class, FragmentConfiguration.class); + addDependents(WebAppConfiguration.class); + + hide("jakarta.security.auth.message."); + } + + @Override + public boolean isAvailable() + { + try + { + return Loader.loadClass("org.eclipse.jetty.security.jaspi.JaspiAuthenticator") != null; + } + catch (Throwable e) + { + LOG.trace("IGNORED", e); + return false; + } + } +} diff --git a/jetty-webapp/src/main/resources/META-INF/services/org.eclipse.jetty.webapp.Configuration b/jetty-webapp/src/main/resources/META-INF/services/org.eclipse.jetty.webapp.Configuration index 9ec24ad9484..3b5d9e771de 100644 --- a/jetty-webapp/src/main/resources/META-INF/services/org.eclipse.jetty.webapp.Configuration +++ b/jetty-webapp/src/main/resources/META-INF/services/org.eclipse.jetty.webapp.Configuration @@ -1,6 +1,7 @@ org.eclipse.jetty.webapp.FragmentConfiguration org.eclipse.jetty.webapp.JettyWebXmlConfiguration org.eclipse.jetty.webapp.JaasConfiguration +org.eclipse.jetty.webapp.JaspiConfiguration org.eclipse.jetty.webapp.JmxConfiguration org.eclipse.jetty.webapp.JndiConfiguration org.eclipse.jetty.webapp.JspConfiguration diff --git a/pom.xml b/pom.xml index f7c6d334328..44838109080 100644 --- a/pom.xml +++ b/pom.xml @@ -1054,6 +1054,16 @@ jakarta.annotation-api ${annotation-api.version} + + jakarta.activation + jakarta.activation-api + 2.0.0 + + + jakarta.authentication + jakarta.authentication-api + 2.0.0 + jakarta.inject jakarta.inject-api @@ -1079,11 +1089,6 @@ asm-analysis ${asm.version} - - org.eclipse.jetty.orbit - javax.security.auth.message - 1.0.0.v201108011116 - org.mortbay.jasper