Merge pull request #6649 from eclipse/jetty-10.0.x-jetty-jaspi

Issue #6406 - backport of jetty-jaspi changes to jetty-10 (#6412)
This commit is contained in:
Lachlan 2021-08-27 13:20:51 +10:00 committed by GitHub
commit f10ae7c955
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 1234 additions and 226 deletions

View File

@ -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[]
----
==== JSR 196: Java Authentication Service Provider Interface for Containers (JASPI)
Jetty can utilize portable authentication modules that implements the Jakarta Authentication specification. This requires the jetty-jaspi module.
Only modules conforming to the ServerAuthModule interface in the https://www.jcp.org/en/jsr/detail?id=196[JASPI Spec] are supported. These modules must be configured before start-up.
The following illustrates a jetty module setting up HTTP Basic Authentication using an 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.

View File

@ -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[]

View File

@ -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 JSR 196 (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 Java Authentication to the Jetty Security framework.
Only modules conforming to the "Servlet Container Profile" with the ServerAuthModule interface within the https://www.jcp.org/en/jsr/detail?id=196[JASPI Spec] 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 Java 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[]
----

View File

@ -80,17 +80,5 @@
<version>1.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.geronimo.components</groupId>
<artifactId>geronimo-jaspi</artifactId>
<version>2.0.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-jaspic_1.0_spec</artifactId>
</exclusion>
</exclusions>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
<Configure>
<Call class="javax.security.auth.message.config.AuthConfigFactory" name="getFactory">
<!-- Configure the AuthConfigFactory here. -->
</Call>
</Configure>

View File

@ -0,0 +1,20 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<!-- ===================================================================== -->
<!-- Configure a factory for Jaspi -->
<!-- ===================================================================== -->
<Call class="javax.security.auth.message.config.AuthConfigFactory" name="setFactory">
<Arg>
<New id="jaspiAuthConfigFactory" class="org.eclipse.jetty.security.jaspi.DefaultAuthConfigFactory" />
</Arg>
</Call>
<Call name="addBean">
<Arg>
<Ref refid="jaspiAuthConfigFactory" />
</Arg>
<Arg type="boolean">false</Arg>
</Call>
</Configure>

View File

@ -0,0 +1,48 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
<Configure>
<Call class="javax.security.auth.message.config.AuthConfigFactory" name="getFactory">
<Call name="registerConfigProvider">
<!-- The Jetty provided implementation of AuthConfigProvider which will wrap a ServerAuthModule. -->
<Arg type="String">org.eclipse.jetty.security.jaspi.provider.JaspiAuthConfigProvider</Arg>
<!-- A Map of initialization properties. -->
<Arg>
<Map>
<Entry>
<!-- Provide the fully qualified classname of the ServerAuthModule to be used. -->
<Item>ServerAuthModule</Item>
<Item>org.eclipse.jetty.security.jaspi.modules.BasicAuthenticationAuthModule</Item>
</Entry>
<Entry>
<!-- Realm as utilised by Jetty Security -->
<Item>org.eclipse.jetty.security.jaspi.modules.RealmName</Item>
<Item>Test Realm</Item>
</Entry>
</Map>
</Arg>
<!-- Message Layer Identifier as per spec chapter 3.1 -->
<Arg type="String">HttpServlet</Arg>
<!-- Application Context Identifier as per spec chapter 3.2
AppContextID ::= hostname blank context-path
The algorithm applied here will use the
_serverName on the configured JaspiAuthenticatorFactory (if set) and try to match it
against the "server" part (in the "server /test" example below).
Next it will try to match the ServletContext#getVirtualServerName to the "server" part.
If neither are set, it will then try to match the first Subject's principal name, and finally fall back to
the default value "server" if none are available.
The context-path should match the context path where this applies.
-->
<Arg type="String">server /test</Arg>
<!-- A friendly description of the provided auth-module. -->
<Arg type="String">A simple provider using HTTP BASIC authentication.</Arg>
</Call>
</Call>
</Configure>

View File

@ -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

View File

@ -0,0 +1,16 @@
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description]
Enables JASPI basic authentication the /test context path.
[tags]
security
[depend]
jaspi
[xml]
etc/jaspi/jaspi-demo.xml
[files]
basehome:etc/jaspi/jaspi-demo.xml|etc/jaspi/jaspi-demo.xml

View File

@ -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

View File

@ -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 javax.security.auth.message;
requires jetty.servlet.api;
requires transitive org.eclipse.jetty.security;
requires org.slf4j;

View File

@ -0,0 +1,249 @@
//
// ========================================================================
// 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 javax.security.auth.AuthPermission;
import javax.security.auth.message.config.AuthConfigFactory;
import javax.security.auth.message.config.AuthConfigProvider;
import javax.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<String, DefaultRegistrationContext> _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(new AuthPermission("registerAuthConfigProvider"));
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(new AuthPermission("registerAuthConfigProvider"));
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(new AuthPermission("removeAuthRegistration"));
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(new AuthPermission("detachAuthListener"));
List<String> 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<String> 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(new AuthPermission("refreshAuth"));
// 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<RegistrationListener> _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);
}
}
}

View File

@ -15,6 +15,7 @@ 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;
@ -22,6 +23,9 @@ 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.AuthConfigFactory;
import javax.security.auth.message.config.AuthConfigProvider;
import javax.security.auth.message.config.RegistrationListener;
import javax.security.auth.message.config.ServerAuthConfig;
import javax.security.auth.message.config.ServerAuthContext;
import javax.servlet.ServletRequest;
@ -30,57 +34,100 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.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 Javax 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<Principal> 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;
}
}
}

View File

@ -14,16 +14,10 @@
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 javax.servlet.ServletContext;
import org.eclipse.jetty.security.Authenticator;
@ -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;
/**
* Javax Authentication (JASPI) Authenticator Factory.
*
* This is used to link a jetty-security {@link Authenticator.Factory} to a Javax Authentication {@link AuthConfigFactory}.
* <p>
* This should be initialized with the provided {@link DefaultAuthConfigFactory} to set up Javax Authentication {@link AuthConfigFactory} before use.
* (A different {@link AuthConfigFactory} may also be provided using the same steps below)
* <p>
* To initialize either:
* <ul>
* <li>invoke {@link AuthConfigFactory#setFactory(AuthConfigFactory)}</li>
* <li>Alternatively: set {@link AuthConfigFactory#DEFAULT_FACTORY_SECURITY_PROPERTY}</li>
* </ul>
*
*/
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)

View File

@ -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<CallerPrincipalCallback> _callerPrincipals = new ThreadLocal<CallerPrincipalCallback>();
private final ThreadLocal<GroupPrincipalCallback> _groupPrincipals = new ThreadLocal<GroupPrincipalCallback>();
private final ThreadLocal<CallerPrincipalCallback> _callerPrincipals = new ThreadLocal<>();
private final ThreadLocal<GroupPrincipalCallback> _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);

View File

@ -15,11 +15,14 @@ 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;
/**
* @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 +38,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;
}

View File

@ -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 Javax 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});

View File

@ -11,7 +11,7 @@
// ========================================================================
//
package org.eclipse.jetty.security.jaspi;
package org.eclipse.jetty.security.jaspi.modules;
import java.io.IOException;
import java.util.Map;
@ -22,46 +22,45 @@ 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.module.ServerAuthModule;
import javax.servlet.http.HttpServletRequest;
import javax.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());
}

View File

@ -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;
}
}

View File

@ -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 javax.security.auth.message.config.AuthConfigFactory;
import javax.security.auth.message.config.AuthConfigProvider;
import javax.security.auth.message.config.ClientAuthConfig;
import javax.security.auth.message.config.ServerAuthConfig;
import javax.security.auth.message.module.ServerAuthModule;
import org.eclipse.jetty.security.jaspi.JaspiAuthenticatorFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>A Jetty implementation of the {@link AuthConfigProvider} to allow registration of a {@link ServerAuthModule}
* directly without having to write a custom {@link AuthConfigProvider}.</p>
* <p>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.</p>
*/
@SuppressWarnings("rawtypes")
public class JaspiAuthConfigProvider implements AuthConfigProvider
{
private static final Logger LOG = LoggerFactory.getLogger(JaspiAuthConfigProvider.class);
private final Map providerProperties;
private final ServerAuthModule serverAuthModule;
/**
* <p>Constructor with signature and implementation that's required by API.</p>
* <p>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.</p>
*
* @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);
}
}
}

View File

@ -0,0 +1,83 @@
//
// ========================================================================
// 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 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 javax.security.auth.message.module.ServerAuthModule;
/**
* Simple implementation of the {@link ServerAuthConfig} interface.
*
* This implementation wires up the given {@link ServerAuthModule} to the appropriate Javax 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()
{
}
}

View File

@ -0,0 +1,58 @@
//
// ========================================================================
// 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 javax.security.auth.message.AuthException;
import javax.security.auth.message.AuthStatus;
import javax.security.auth.message.MessageInfo;
import javax.security.auth.message.config.ServerAuthContext;
import javax.security.auth.message.module.ServerAuthModule;
/**
* Simple bridge implementation of the Javax 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);
}
}

View File

@ -1 +1 @@
org.eclipse.jetty.security.jaspi.JaspiAuthenticatorFactory
org.eclipse.jetty.security.jaspi.JaspiAuthenticatorFactory

View File

@ -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 javax.security.auth.message.config.AuthConfigFactory;
import javax.security.auth.message.config.AuthConfigProvider;
import javax.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<String, String> 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;
}
}
}

View File

@ -20,6 +20,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.security.auth.message.config.AuthConfigFactory;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.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<String, UserPrincipal> _users = new HashMap<>();
protected Map<String, List<RolePrincipal>> _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);

View File

@ -1,39 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<jaspi xmlns="http://geronimo.apache.org/xml/ns/geronimo-jaspi">
<configProvider>
<messageLayer>HTTP</messageLayer>
<appContext>server /ctx</appContext>
<description>description</description>
<serverAuthConfig>
<authenticationContextID>authenticationContextID1</authenticationContextID>
<protected>true</protected>
<serverAuthContext>
<serverAuthModule>
<className>org.eclipse.jetty.security.jaspi.BasicAuthModule</className>
<options>
org.eclipse.jetty.security.jaspi.modules.RealmName=TestRealm
</options>
</serverAuthModule>
</serverAuthContext>
</serverAuthConfig>
<persistent>true</persistent>
</configProvider>
<configProvider>
<messageLayer>HTTP</messageLayer>
<appContext>server /other</appContext>
<description>description</description>
<serverAuthConfig>
<authenticationContextID>authenticationContextID2</authenticationContextID>
<protected>true</protected>
<serverAuthContext>
<serverAuthModule>
<className>org.eclipse.jetty.security.jaspi.HttpHeaderAuthModule</className>
<options>
</options>
</serverAuthModule>
</serverAuthContext>
</serverAuthConfig>
<persistent>true</persistent>
</configProvider>
</jaspi>

View File

@ -0,0 +1,58 @@
//
// ========================================================================
// 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 javax.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)
{
}
}

View File

@ -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<String> 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();
}
}

View File

@ -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,

View File

@ -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;
/**
* <p>JASPI Configuration</p>
* <p>This configuration configures the WebAppContext server/system classes to
* not be able to see the {@code javax.security.auth.message} package.</p>
*/
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("javax.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;
}
}
}

View File

@ -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